React Hook 是 React 16.8 的新特性
调用 useState 函数并设置初始值即可
function Counter() {const [count, setCount] = useState(0);return (<div><div>{count}</div><Button onClick={() => { setCount(count + 1); }}>点击</Button></div>);}
二、useEffect 用法
第二参数有三种设置方法,并自己设置是否返回一个函数,这个函数会在第二次渲染时执行
- useEffect 不设置第二个参数 —— 监听所有 state
- useEffect 设置第二参数 —— 监听数组内的 state
- useEffect 设置第二参数为空数组 —— 只在第一次挂载时执行
1. 用法一
useEffect 不设置第二个参数
function Counter(){const [count, setCount] = useState(0)const [age, setAge] = useState(20);useEffect(()={console.log('组件渲染了') // 监控所有 state})return (<div><div>>{count}</div><button onClick={()=>{setCount(count+1)}}>+1</button><p>{age}</p><button onClick={setAge(age+1)}>+1</button></div>)}
2. 用法二
useEffect 的第二参数设置一个 state,并返回一个函数
function Counter(){const [count, setCount] = useState(0)const [age, setAge] = useState(20);useEffect(()={console.log('挂载和状态更新时执行')return ()=>{console.log('状态更新和卸载组件时执行')}},[count]) // 只监控 count 状态return (<div><div>>{count}</div><button onClick={()=>{setCount(count+1)}}>+1</button><p>{age}</p><button onClick={setAge(age+1)}>+1</button></div>)}
3. 用法三
useEffect 的第二参数设置为空数组
impont React,{useState,useEffect} from 'react'function App(){const [count, setCount] = useState(0)useState(()=>{console.log('首次执行')return ()=>{console.log('仅卸载时执行')}},[]) // 都不监控,只在第一次挂载时执行一次return (<div><div>>{count}</div><button onClick={()=>{setCount(count+1)}}>+1</button><p>{age}</p><button onClick={setAge(age+1)}>+1</button></div>)}
三、模拟 useState 和 useEffect 接口
1. useState 简单实现
核心功能:state 和 setState
const render = () => {ReactDOM.render(<App />,document.querySelector('#root'))}let state = undefined;function useState(initialValue) {if (!state) {state = initialValue}function setState(newState) {state = newState;render();}return [state, setState];}
2. useEffect 简单实现
核心功能
- 第一次渲染组件时执行一次
- 监控数据变化,在发生变化时执行一次
- 第二参数为空数组时,只执行一次
let oldDeps = undefined // 记录上一次数组,第一次当然未定义function useEffect(callback, depArray) {const hasDeps = depArray// 空数组永远返回 true// 有值的数组时,一一对比是否和上一次对比是否一变化,第一次对比一定返回 false,以后有变化时返回 false// depArray.every((item,i)=>item === oldDeps[i]}const hasChangedDeps = oldDeps ? !depArray.every((item, i) => item === oldDeps[i]) : trueif (hasChangedDeps || !hasDeps) {callback()oldDeps = depArray}}
3. 完善
- 以上 state 只是定义了一个全局变量,无法满足多个 state 的情况
let memoizedState = []; // hooks 存放在这个数组,放 useState 的 state 和 useEffect 的监控数组let cursor = 0; // 当前 memoizedState 下标const render = ()=>{ReactDOM.render(<App />,document.querySelector('#root'))}// [100,101]// 0 1 2// 每次使用 useState 时,cursor 会指向数组的下一位// 每个 setState 都会使用之前记录下的 currentCursorfunction useState(initialValue) {memoizedState[cursor] = memoizedState[cursor] || initialValue;const currentCursor = cursor;function setState(newState) {memoizedState[currentCursor] = newState;render();}return [memoizedState[cursor++], setState]; // 返回当前 state,并把 cursor 加 1}function useEffect(callback, depArray) {const hasNoDeps = !depArray;const deps = memoizedState[cursor];const hasChangedDeps = deps? !depArray.every((el, i) => el === deps[i]): true;if (hasNoDeps || hasChangedDeps) {callback();memoizedState[cursor] = depArray;}cursor++;}
- 这是闭包的经典使用
- 真正的 React 里不是用的数组,而是链表,但是原理类似
- 难点是每次 render 后,数组要对上号
