React自定义Hook执行流程及useInputState的onChange更新原理
React自定义Hook数据获取与执行流程解析(以useInputState为例)
自定义Hook的核心本质
自定义Hook没有任何特殊黑魔法,本质是将组件中可复用的状态逻辑抽离为独立函数,运行时严格遵循React Hooks的调用规则,和直接在组件内编写状态逻辑的执行效果完全等价。它不会创建独立的状态作用域,所有状态最终都会绑定到调用该Hook的组件实例上。
示例useInputState逻辑拆解
示例中的输入状态管理Hook代码如下:
import { useState } from "react"; export default initialValue => { const [value, setValue] = useState(initialValue); return { value, onChange: event => { setValue(event.target.value); }, reset: () => setValue("") }; };
这个Hook的基础逻辑非常简单:
- 接收一个初始值作为入参,内部调用React内置的
useState创建状态与对应的更新方法 - 返回一个包含三个成员的对象:
value:当前绑定的输入状态值onChange:适配表单输入事件的回调函数,接收事件对象后自动提取输入框最新值更新状态reset:状态重置方法,调用后直接将输入值清空为空字符串
深度执行流程(结合TodoForm组件)
TodoForm组件调用该Hook实现输入管理的代码如下:
import React from "react"; import TextField from "@material-ui/core/TextField"; import useInputState from "./useInputState"; const TodoForm = ({ saveTodo }) => { const { value, reset, onChange } = useInputState(""); return ( <form onSubmit={event => { event.preventDefault(); saveTodo(value); reset(); }} > <TextField variant="outlined" placeholder="Add todo" margin="normal" value={value} onChange={onChange} /> </form> ); }; export default TodoForm;
整个运行流程可以拆分为三个核心阶段:
- 组件首次挂载阶段
- TodoForm组件进入渲染流程,执行到
useInputState("")时传入初始值空字符串 - 自定义Hook内部执行,调用
useState(""),React会为当前TodoForm组件分配一个独立的状态存储槽位,初始值设为空字符串 - Hook返回包含当前状态值、onChange函数、reset函数的对象,组件解构后拿到这三个成员
- 渲染时将
value绑定到TextField的value属性,将onChange绑定为TextField的输入事件监听函数
- TodoForm组件进入渲染流程,执行到
- 用户输入触发更新阶段
- 用户在输入框输入内容,TextField组件触发change事件,将原生事件对象作为入参传给绑定的onChange函数
- Hook返回的onChange函数通过闭包持有当前组件实例对应的
setValue方法,调用setValue(event.target.value)将用户输入的最新值提交给React - React检测到状态变更,触发TodoForm组件重渲染
- 重渲染时再次调用
useInputState(""),React根据Hook的固定调用顺序定位到之前为该组件分配的状态槽位,返回最新的value值 - 最新的value传递给TextField组件,输入框同步显示用户输入的内容
- 表单提交重置阶段
- 用户提交表单,触发form元素的submit事件,先调用
event.preventDefault()阻止浏览器默认跳转行为,再调用saveTodo(value)将输入内容传给父组件,最后调用reset()方法 - reset方法同样通过闭包持有当前组件的
setValue,调用setValue("")将状态重置为空字符串 - 组件触发重渲染,输入框回到空值状态
- 用户提交表单,触发form元素的submit事件,先调用
核心疑问解答:解构得到的onChange为什么可以正常更新数据?
从Hook中解构出的onChange不需要在组件内额外定义就能正常工作,核心原因有两点:
- 闭包与状态绑定关系正确:自定义Hook每次随组件渲染执行时,内部定义的onChange、reset函数都会通过闭包捕获当前组件实例对应的
setValue方法,这个方法和组件内部直接调用useState拿到的更新方法完全一致,调用它就相当于直接在组件内更新状态,不存在作用域隔离的问题。- 事件回调只认函数签名不认定义位置:React的onChange事件属性只要求传入的参数是可以接收事件对象作为入参的函数,完全不关心这个函数是定义在组件内部、从Hook返回,还是从外部工具模块导入。只要传入的函数符合参数要求,事件触发时就会被正常调用。
实际上,使用自定义Hook和直接在组件内写状态逻辑是100%等价的,以下是不用Hook的等价实现,运行效果没有任何区别:
const TodoForm = ({ saveTodo }) => { // 以下代码就是useInputState的内联写法 const [value, setValue] = useState(""); const onChange = event => setValue(event.target.value); const reset = () => setValue(""); return ( <form onSubmit={event => { event.preventDefault(); saveTodo(value); reset(); }} > <TextField variant="outlined" placeholder="Add todo" margin="normal" value={value} onChange={onChange} /> </form> ); };
自定义Hook的价值仅仅是把这部分重复的输入状态逻辑抽离复用,当项目中有多个表单输入组件时,不需要重复编写相同的useState、事件回调、重置逻辑,直接调用Hook即可。
内容的提问来源于stack exchange,提问作者Saurabh Suman
相关产品推荐
相关产品推荐

