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]) : true
if (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 都会使用之前记录下的 currentCursor
function 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 后,数组要对上号