- Hook 是 React 16.8 新增特性,可以在不编写class的情况下使用state以及其他的React特性
解决的问题
1、 组件之间复用状态逻辑很难,可能用到render props和高阶组件,Hook为共享状态提供了更好的原生途径<br /> 2、Hook 将组件中相互关联部分拆分成更小的函数<br /> 3、解决了class难以理解和this问题
注意事项
1、只能在函数最外层调用Hook,不要在循环、条件判断或者子函数中调用<br /> 2、只能在React的函数组件中调用Hook,不要在其他JavaScript函数中调用
一、useState 钩子
1、原理
```javascript import React from ‘react’; import ReactDOM from ‘react-dom’;
let lastState; function useState(initialState){ lastState = lastState || initialState function setState(newState){ lastState = newState render() } return [lastState,setState] }
function Counter(){ // const [name,setName] =React.useState(‘rock’) const [name,setName] = useState(‘rock’) return(
function render(){
ReactDOM.render(
<a name="IxSFQ"></a>### 2、补充原理,多个useState```javascriptimport React from 'react';import ReactDOM from 'react-dom';let hookStates = []; //保存状态的数组let hookIndex = 0; //索引function useState(initialState) {hookStates[hookIndex] = hookStates[hookIndex] || initialState;let currentIndex = hookIndex;function setState(newState) {if (typeof newState === 'function') {newState = newState(hookStates[currentIndex]);}hookStates[currentIndex] = newState;render();}return [hookStates[hookIndex++], setState];}function Counter() {const [name, setName] = useState('rock');const [number, setNumber] = useState(0);return (<div><input value={name} onChange={(e) => setName(e.target.value)} />{name}<input value={number} onChange={(e) => setNumber(e.target.value)} />{number}</div>);}function render() {hookIndex = 0;ReactDOM.render(<Counter />, document.getElementById('root'));}render();
二、UseMemo 钩子
减少渲染次数,提高性能
import React,{useState} from "react"import ReactDOM from 'react-dom';let Child=({data,onButtonClick})=>{console.log('Child render')return <button onClick={onButtonClick}>{data.number}</button>}function Counter(){const [number,setNumber] = React.useState(0)const [name,setName] =React.useState('zhufeng')let data ={number}let addClick =()=> setNumber(number+1)return(<div><input value={name} onChange={e=>setName(e.target.value)}/><Child data={data} onButtonClick={addClick}/></div>)}function render(){ReactDOM.render(<Counter/>,document.getElementById('root'))}render();
1、 使用memo、useCallback优化
为了减少组件渲染,我们可以优化,设置组件的属性变了才重新渲染,如果没有变则不渲染
import React,{useState} from "react"import ReactDOM from 'react-dom';let Child=({data,onButtonClick})=>{console.log('Child render')return <button onClick={onButtonClick}>{data.number}</button>}Child = React.memo(Child) // 传入函数组件,返回一个纯组件, 也是一个函数/*function memo(OldFunctionComponent){return class extends React.PureComponent{return <OldFunctionComponent {...this.props}/>}}*/// let MyData = {number:0};// let lastData;function App(){const [number,setNumber] = React.useState(0)const [name,setName] =React.useState('zhufeng')// 每次渲染app都会形成一个新对象,memo是浅比较,会比较地址// let data ={number}// 改成memo 1 参数是生成对象的工厂, 2参数是依赖的变量let data = React.useMemo(()=>({number}),[number])console.log(data === lastData)lastData = data// 每次渲染App都声明了一个新的函数// let addClick =()=> setNumber(number+1)// 改成useCallback优化let addClick =React.useCallback(()=> setNumber(number+1),[number])return(<div><input value={name} onChange={e=>setName(e.target.value)}/><Child data={data} onButtonClick={addClick}/></div>)}function render(){ReactDOM.render(<Counter/>,document.getElementById('root'))}render();
2、 实现use
UseMemo
function useMemo(factory, dependencies) {if (hookStates[hookIndex]) {// 说明不是第一次let [lastMemo, lastDependencies] = hookStates[hookIndex];// 判断一下新的依赖数组中的每一项是否跟上一次完全相等let same = dependencies.every((item, index) => item === lastDependencies[index]);if (same) {hookIndex++;return lastMemo;} else {// 只要一个依赖变量不一样的话let newMemo = factory();hookStates[hookIndex++] = [newMemo, dependencies];return newMemo;}} else {// 说明是第一次渲染let newMemo = factory();hookStates[hookIndex++] = [newMemo, dependencies];return newMemo;}}// ... 使用自己的useState// ... 验证 使用了useMemo后, name发生变化的时候,data不变, data 和 lastData的引用地址相同
useCallback
function useCallback(callback,dependencies){if(hookStates[hookIndex]){ // 说明不是第一次let [lastCallback,lastDependencies] = hookStates[hookIndex]// 判断一下新的一览数组中的每一项是否跟上一次完全相等let same = dependencies.every((item,index)=> item === lastDependencies[index])if(same){hookIndex++;return lastCallback}else{ // 只要一个依赖变量不一样的话hookStates[hookIndex++] = [callback,dependencies];return callback;}}else{ // 说明是第一次hookStates[hookIndex++] = [callback,dependencies]return callback}}// ... 使用自己的useState
三、useEffect 钩子
- useEffect
- 在函数组件主体内,改变DOM、添加订阅、设置定时器、记录日志等副作用都是不允许的,因为会产生莫名bug并破坏UI的一致性
- useEffect就是一个Effect Hook,给函数组件增加来操作副作用的能力,它跟class组件中的生命周期一致
- 该Hook接收一个包含命令式,且可能有副作用代码的函数
useEffect(didUpdate)
1、使用
import React,{useState} from "react"
import ReactDOM from 'react-do\\\\\\\\\
let Counter=()=>{
const [number,setNumber] = React.useState(0)
const [name,setName] =React.useState('zhufeng')
React.useEffect(()=>{
// 副作用钩子,让浏览器的title发生变化,依赖number
document.title = number;
console.log(number);
},[number]);
return(
<div>
<p>number:{number}</p>
<p>name:{name}</p>
<input value={name} onChange={e=>setName(e.target.value)}/>
<button onClick={()=>setNumber(number+1)}>+</button>
</div>
)
}
function render(){
ReactDOM.render(
<Counter/>,
document.getElementById('root')
)
}
render();
2、原理
function useEffect(callback, dependencies) {
if (hookStates[hookIndex]) {
// 说明不是第一次
let lastDependencies = hookStates[hookIndex];
// 判断一下新的一览数组中的每一项是否跟上一次完全相等
let same = dependencies.every(
(item, index) => item === lastDependencies[index]
);
if (same) {
hookIndex++;
} else {
// 只要一个依赖变量不一样的话
hookStates[hookIndex++] = dependencies;
// 添加一个宏任务,在本次渲染之后执行
setTimeout(callback)
}
} else {
// 说明是第一次
hookStates[hookIndex++] = dependencies;
setTimeout(callback)
}
}
// ... 使用自己的useState
模拟生命周期
useEffect(()=>{
let timer = setTinerval(()=>{
setNumber(state=>state+1)
},1000)
// 返回一个销毁函数
return ()=> {
console.log('销毁')
clearInterval(timer)
}
},[number])
3、实现内部返回函数模拟生命周期的原理
function useEffect(callback, dependencies) {
if (hookStates[hookIndex]) {
// 说明不是第一次
let [oldDestrory,lastDependencies] = hookStates[hookIndex];
let same = dependencies.every(
(item, index) => item === lastDependencies[index]
);
if (same) {
hookIndex++;
} else {
oldDestrory()
let destroy = callback();
hookStates[hookIndex++] = [destroy,dependencies]
}
} else {
// 说明是第一次
let destroy = callback();
hookStates[hookIndex++] = [destroy,dependencies];
}
}
// ... 使用自己的useState
四、useLayoutEffect 钩子
与useEffect的区别,useEffect内部宏任务执行。useLayoutEffect内部微任务执行
useEffect: 添加一个宏任务,在本次渲染之后执行
useLayoutEffect: 添加一个微任务, 在浏览器渲染之前执行
1、例子:

import React, { useState } from 'react';
import ReactDOM from 'react-dom';
function Animation() {
const red = React.useRef(); // {current:null}
const green = React.useRef(); // {current:null}
// 他会增加一个微任务,主栈执行结束后,先清空微任务,在进行浏览器渲染
React.useLayoutEffect(() => {
red.current.style.transform = 'translate(500px)';
red.current.style.transition = 'all 500ms';
});
React.useEffect(() => {
green.current.style.transform = 'translate(500px)';
green.current.style.transition = 'all 500ms';
});
let style = { width: '100px', height: '100px' };
return (
<div>
<div style={{ ...style, background: 'red' }} ref={red}></div>
<div style={{ ...style, background: 'green' }} ref={green}></div>
</div>
);
}
function render() {
ReactDOM.render(<Animation />, document.getElementById('root'));
}
render();
2、实现:
function useLayoutEffect(callback, dependencies) {
if (hookStates[hookIndex]) {
// 说明不是第一次
let lastDependencies = hookStates[hookIndex];
// 判断一下新的一览数组中的每一项是否跟上一次完全相等
let same = dependencies.every(
(item, index) => item === lastDependencies[index]
);
if (same) {
hookIndex++;
} else {
// 只要一个依赖变量不一样的话
hookStates[hookIndex++] = dependencies;
// 添加一个微任务, 在浏览器渲染之前执行
queryMicrotask(callback)
}
} else {
// 说明是第一次
hookStates[hookIndex++] = dependencies;
queryMicrotask(callback)
}
}
// ... 使用自己的useState
五、useContext
- 接受一个 context 对象(React.createContext的返回值)并返回该 context 的当前值
- 当前context值由上层组件中距离当前组件最近的
的value prop决定 - 当组件上层最近的
更新时,该Hook会触发重渲染,并使用最新传递给MyContext.Provider的context value值 - useContext相当于class组件中的 static.contextType = myContext 或者
- useContext只是让你能读取context的值以及订阅context的变化,仍然要在上层组件中使用
来为下层组件提供value
1、 使用
import React, { useState,useContext } from 'react';
import ReactDOM from 'react-dom';
let ConterContext = React.createContext(); // ConterContext.Provider CounterContext.Consumer
// 使用方法1
function Counter() {
let { state, setState } = useContext(ConterContext);
return (
<div>
<p>{state.number}</p>
<button onClick={() => setState({ number: state.number + 1 })}>+</button>
</div>
);
}
// 使用方法二
// 或者用CounterContext.Consumer
function Conter2() {
// let { state, setState } = React.useContext(ConterContext);
return (
<ConterContext.Consumer>
{(value) => (
<div>
<p>{value.state.number}</p>
<button
onClick={() => value.setState({ number: value.state.number + 1 })}
>
+
</button>
</div>
)}
</ConterContext.Consumer>
);
}
function App() {
const [state, setState] = React.useState({ number: 0 });
return (
<ConterContext.Provider value={{ state, setState }}>
<Conter2 />
</ConterContext.Provider>
);
}
function render() {
ReactDOM.render(<App />, document.getElementById('root'));
}
render();
2、实现
function useContext(context){
return context._currentValue;
}
六、useReducer
- useState的替代方案,是useState的语法糖,接收一个形如(state,action) => newState的reducer,返回当前的state以及与其配套的dispatch方法
- 在某些场景下,useReducer会比useState更适用, 例如 state逻辑较复杂并且包含多个子值,或者下一个state依赖于之前的state等
1、使用
const [state,dispach] = useReducer(reducer, initialArg, init)
import React, { useState, useReducer } from 'react';
import ReactDOM from 'react-dom';
function reducer(state, action) {
switch (action.type) {
case 'add':
return {number: state.number + 1 };
case 'decrement':
return {number: state.number - 1 };
default:
return state;
}
}
let initState = { number:0 }
function Conter() {
let [state, dispatch] = useReducer(reducer, initState );
return (
<div>
<p>{state}</p>
<button onClick={() => dispatch({ type: 'add' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
function render() {
ReactDOM.render(<Conter />, document.getElementById('root'));
}
render();
用useState 实现 useReducer
function reducer(state, action) {
switch (action.type) {
case 'add':
return number+1;
default:
return state;
}
}
function useState(initialState) {
let reducer = useCallback((state,action)=>action)
let [state,setState] = useReducer(reducer,initialState);
function setState(payload){
dispatch(payload)
}
return [state,setState]
}
function App(){
let [number,setNumber] = useState(0)
return <div>
<p>{number}</p>
<button onClick={() => dispatch({ type: 'add' })}>+</button>
</div>
}
2、实现
let hookStates = [] //保存状态的数组
let hookIndex =0; //索引
function useReducer(reducer,initialState){
hookStates[hookIndex] = hookStates[hookIndex] || initialState;
let currentIndex = hookIndex;
function dispatch(action){
hookStates[currentIndex] = reducer ? reducer(hookStates[currentIndex],action): action;
render()
}
return [hookStates[hookIndex++],dispatch];
}
// ...
function render() {
hookIndex = 0;
ReactDOM.render(<Conter />, document.getElementById('root'));
}
3、为什么说useReducer是useState的语法糖
function useState(initialState){
return useReducer(null,initialState)
}
七、useRef 钩子
可以使用这个获取最新的值
function useRef(value){
lastRef = lastRef || { current:value };
return lastRef;
}
八、memo
Child = memo(Child)
让组件具有记忆的功能,当组件的属性发生变更的时候才会刷新,否则不刷新
九、useCallback
1、使用
参数callbackFn,依赖项
let lastAddClick;
function App(){
const data = {number:1} // 每次都会产生一个新的对象,导致多次计算
return (...)
}
let lastDate;
// 使用useMemo缓存值
function App(){
let addCounter = useCallback(()=>setNumber(x=>x+1),[])
console.log("addCounter ===lastDate", addCounter ===lastDate)
lastDate = addCounter
return (...)
}
十、useMemo
1、使用
缓存值
function App(){
const data = {number:1} // 每次都会产生一个新的对象,导致多次计算
return (...)
}
let lastDate;
// 使用useMemo缓存值
function App(){
const data = useMemo(()=>({number:1}),[number]) // 每次都会产生一个新的对象,导致多次计算
console.log("lastDate === data", lastDate === data)
lastDate = data
return (...)
}
十一、注意事项
使用HOOKS , 只能再函数外层调用Hook,不要再循环、条件判断或者子函数中调用。
1、例子
function App(){
let [number,setNumber] = useState(0)
let [visible,setVisible] = useState(true)
if(number % 2 == 1){
useEffect(()=>{
});
}
return <div>
<p>{number}</p>
{visible && <div> visible </div>}
</div>
}
// error: ...
2、原因
在if或者for循环中,影响了两次hook执行的数量,会影响了react的diff对比
