1.为什么应用Hooks
副作用问题:诸如数据获取、订阅、定时执行工作、手动批改ReactDOM这些行为都能够称为副作用;而Hooks的呈现能够应用useEffect来解决这些副作用
简单的状态治理:之前通常应用redux、mobx这些第三方状态管理器来治理简单的状态,而Hooks能够应用useReducer、useContext配合实现简单的状态治理;
开发效率和品质问题:函数式组件比类组件简洁,效率高,性能也好。
2.useState
const [state, setState] = useState(initState)
2.1每次渲染都是独立的闭包
- 每次渲染都有本人的props和state
- 每次渲染都有本人的事件处理函数
- 组件函数每次渲染都会被调用,每一次调用中number的值都是常量,并且并赋予了以后渲染中的状态值
- 在单次渲染中,props和state始终不变
function Counter1() { let [info, setInfo] = useState({ number: 0 }); const alertNumber = () => { setTimeout(() => { alert(info.number); }, 3000); } return ( <div> <p>{info.number}</p> <button onClick={() => setInfo({ number: info.number + 1 })}>+</button> <button onClick={alertNumber}>alertNumber</button> </div> )}
2.2函数式更新
如果新的state须要应用先前的state计算得出,能够将函数传递给setState, 该函数将接管先前的state值,并返回一个更新后的值
function Counter2() { let [info, setInfo] = useState({ number: 0 }); function lazy() { setTimeout(() => { setInfo({ number: info.number + 1 }) }, 2000); } function lazyFunction() { setTimeout(() => { setInfo(info => ({ number: info.number + 1 })); setInfo(info => ({ number: info.number + 1 })); }, 3000); } return ( <div> <p>{info.number}</p> <button onClick={() => setInfo({ number: info.number + 1 })}>+</button> <button onClick={lazy}>lazy</button> <button onClick={lazyFunction}>lazyFunction</button> </div> )}
2.3惰性初始化
- initState参数只会在初始渲染中起作用,后续渲染会被疏忽
- 如果初始state须要通过简单计算取得,则能够传入一个函数,在函数中计算返回初始state, 此函数只在初始渲染时被调用
- 与class的setState办法不同,useState不会主动合并更新对象,能够用函数式的setState联合开展运算符达到合并对象的成果
function Counter3() { let [info, setInfo] = useState(function () { console.log('初始状态-----') return { number: 0, name: "计数器" }; }); console.log("Counter5 render"); return ( <div> <p> {info.name}:{info.number} </p> <button onClick={() => setInfo({ ...info, number: info.number + 1 })}> + </button> <button onClick={() => setInfo(info)}>+</button> </div> );}
3.useEffect
useEffect(callback, array)
:副作用解决的钩子;它也是componentDidMount()、componentDidUpdate()、componentWillUnmount()、
这几个生命周期办法的对立,一个顶三个!
- 第二个参数如果不写,只有状态扭转都会执行。
- 第二个参数是个空数组时,不论哪个状态扭转都不执行,只在组件初始时执行一次。
- 当第一个回调函数中有return返回值时,示意componentWIllUnmount时执行
4.性能优化
- 把回调函数和依赖项数组作为参数传入 useCallback, 将返回该回调函数的memoized版本,该回调函数仅在某个依赖项扭转时才会更新。 (管制组件什么时候更新)
- 把创立函数和依赖项数组作为参数传入 useMemo 仅会在某个依赖项扭转时才从新计算memoized值 这种优化有助于防止在每次渲染时都进行高开销的计算。 (管制组件是否更新)
let lastAddClick;let lastChangeName;function Counter4() { let [number, setNumber] = useState(0); let [name, setName] = useState('zhufeng'); //会在每次渲染的时候都 会生成一个新的函数 //只有在依赖的变量发生变化的时候才会从新生成 const addClick = useCallback(() => setNumber(number + 1), [number]); console.log(lastAddClick === addClick); lastAddClick = addClick; const changeName = useCallback(() => setName(Date.now()), [name]); console.log(lastChangeName === changeName); lastChangeName = changeName; return ( <div> <p>{name}:{number}</p> <button onClick={addClick}>addClick</button> <button onClick={changeName}>changeName</button> </div> )}
5.Hooks规定
自定义 Hook 必须以 “**use**
” 结尾吗?必须如此。这个约定十分重要。不遵循的话,因为无奈判断某个函数是否蕴含对其外部 Hook 的调用,React 将无奈主动查看你的 Hook 是否违反了 Hook 的规定。
- 只能在react的函数组件应用调用hook
- 须要在组件顶层调用,不能在判断啊,循环里调用,因为hook是按程序执行的,退出放在判断里,第一个调用了,第二次没调用,前面的hook调用提前执行,会导致bug。
每一个 Hook 都有的两个相干函数:mountXxx()
和 updateXxx()
,它们别离是 Hook 在 Mount 阶段(即组件的挂载、或者说初始化阶段、又或者说是第一次执行 useXxx()的时候)和 Update阶段(即组件的更新、或者说组件从新渲染阶段)的逻辑。为了方便管理和调用,react 的工程师把 Hook 在 Mount 阶段的逻辑存到 (HooksDispatcherOnMount
) 对象中,把 Update 阶段的逻辑存到 (HooksDispatcherOnUpdate
) 对象中
const HooksDispatcherOnMount: Dispatcher = { readContext, useCallback: mountCallback, useContext: readContext, useEffect: mountEffect, useImperativeHandle: mountImperativeHandle, useLayoutEffect: mountLayoutEffect, useMemo: mountMemo, useReducer: mountReducer, useRef: mountRef, useState: mountState, useDebugValue: mountDebugValue, useResponder: createDeprecatedResponderListener, useDeferredValue: mountDeferredValue, useTransition: mountTransition,};const HooksDispatcherOnUpdate: Dispatcher = { readContext, useCallback: updateCallback, useContext: readContext, useEffect: updateEffect, useImperativeHandle: updateImperativeHandle, useLayoutEffect: updateLayoutEffect, useMemo: updateMemo, useReducer: updateReducer, useRef: updateRef, useState: updateState, useDebugValue: updateDebugValue, useResponder: createDeprecatedResponderListener, useDeferredValue: updateDeferredValue, useTransition: updateTransition,};
Hook 在 Mount 阶段干了啥?
用 useState 为例。useState 在 Mount 阶段的逻辑写在 mountState()
办法中:
- 获取以后 Hook 节点,同时将以后 Hook 增加到 Hook 链表中
- 初始化 Hook 的状态,即读取初始 state 值
- 创立一个新的链表作为更新队列,用来寄存更新操作(setXxx())
- 创立一个 dispatch 办法(即 useState 返回的数组的第二个参数:setXxx()),该办法的用处是用来批改 state,并将此更新操作增加到更新队列中,另外还会将该更新和以后正在渲染的 fiber 绑定起来
- 返回以后 state 和 批改 state 的办法(dispatch)
function mountState<S>( initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] { // 获取以后 Hook 节点,同时将以后 Hook 增加到 Hook 链表中 const hook = mountWorkInProgressHook(); // 初始化 Hook 的状态,即读取初始 state 值 if (typeof initialState === 'function') { initialState = initialState(); } hook.memoizedState = hook.baseState = initialState; // 创立一个新的链表作为更新队列,用来寄存更新(setXxx()) const queue = (hook.queue = { pending: null, dispatch: null, lastRenderedReducer: basicStateReducer, lastRenderedState: (initialState: any), }); // 创立一个 dispatch 办法(即 useState 返回的数组的第二个参数:setXxx()), // 该办法的作用是用来批改 state,并将此更新增加到更新队列中,另外还会将改更新和以后正在渲染的 fiber 绑定起来 const dispatch: Dispatch< BasicStateAction<S>, > = (queue.dispatch = (dispatchAction.bind( null, currentlyRenderingFiber, queue, ): any)); // 返回以后 state 和 批改 state 的办法 return [hook.memoizedState, dispatch];}
存储 Hook 的数据结构——链表
一个函数组件中的所有 Hook 是以 链表 的模式存储的。链表中的每个节点就是一个 Hook
export type Hook = { memoizedState: any, // Hook 本身保护的状态 ... queue: UpdateQueue<any, any> | null, // Hook 本身保护的更新队列 next: Hook | null, // next 指向下一个 Hook};
const [firstName, setFirstName] = useState('尼古拉斯');const [lastName, setLastName] = useState('赵四');useEffect(() => {})
useState 如何解决 state 更新
更新队列链表 queue
,用来寄存更新操作,链表中的每一个节点就是一次更新 state 的操作(就是调用了一次 setXxx()),以便前面 Update 阶段能够拿到最新的 state。
const queue = (hook.queue = { pending: null, dispatch: null, lastRenderedReducer: basicStateReducer, lastRenderedState: (initialState: any),});
pending
:最近一个期待执行的更新
dispatch
:更新 state 的办法(setXxx)
lastRenderedReducer
: 组件最近一次渲染时用的 reducer (useState 实际上是一个简化版的 useReducer,之所以用户在应用 useState 时不须要传入 reducer,是因为 useState 默认应用 react 官网写好的 reducer:basicStateReducer
)
lastRenderedState
:组件最近一次渲染的 state