• Hook 是 React 16.8 新增特性,可以在不编写class的情况下使用state以及其他的React特性
  • 解决的问题

    1. 1 组件之间复用状态逻辑很难,可能用到render props和高阶组件,Hook为共享状态提供了更好的原生途径<br /> 2Hook 将组件中相互关联部分拆分成更小的函数<br /> 3、解决了class难以理解和this问题
  • 注意事项

    1. 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(

setName(e.target.value)}/> {name}
) }

function render(){ ReactDOM.render( , document.getElementById(‘root’) ); } render()

  1. <a name="IxSFQ"></a>
  2. ### 2、补充原理,多个useState
  3. ```javascript
  4. import React from 'react';
  5. import ReactDOM from 'react-dom';
  6. let hookStates = []; //保存状态的数组
  7. let hookIndex = 0; //索引
  8. function useState(initialState) {
  9. hookStates[hookIndex] = hookStates[hookIndex] || initialState;
  10. let currentIndex = hookIndex;
  11. function setState(newState) {
  12. if (typeof newState === 'function') {
  13. newState = newState(hookStates[currentIndex]);
  14. }
  15. hookStates[currentIndex] = newState;
  16. render();
  17. }
  18. return [hookStates[hookIndex++], setState];
  19. }
  20. function Counter() {
  21. const [name, setName] = useState('rock');
  22. const [number, setNumber] = useState(0);
  23. return (
  24. <div>
  25. <input value={name} onChange={(e) => setName(e.target.value)} />
  26. {name}
  27. <input value={number} onChange={(e) => setNumber(e.target.value)} />
  28. {number}
  29. </div>
  30. );
  31. }
  32. function render() {
  33. hookIndex = 0;
  34. ReactDOM.render(<Counter />, document.getElementById('root'));
  35. }
  36. render();

二、UseMemo 钩子

减少渲染次数,提高性能

  1. import React,{useState} from "react"
  2. import ReactDOM from 'react-dom';
  3. let Child=({data,onButtonClick})=>{
  4. console.log('Child render')
  5. return <button onClick={onButtonClick}>{data.number}</button>
  6. }
  7. function Counter(){
  8. const [number,setNumber] = React.useState(0)
  9. const [name,setName] =React.useState('zhufeng')
  10. let data ={number}
  11. let addClick =()=> setNumber(number+1)
  12. return(
  13. <div>
  14. <input value={name} onChange={e=>setName(e.target.value)}/>
  15. <Child data={data} onButtonClick={addClick}/>
  16. </div>
  17. )
  18. }
  19. function render(){
  20. ReactDOM.render(
  21. <Counter/>,
  22. document.getElementById('root')
  23. )
  24. }
  25. render();

1、 使用memo、useCallback优化

为了减少组件渲染,我们可以优化,设置组件的属性变了才重新渲染,如果没有变则不渲染

  1. import React,{useState} from "react"
  2. import ReactDOM from 'react-dom';
  3. let Child=({data,onButtonClick})=>{
  4. console.log('Child render')
  5. return <button onClick={onButtonClick}>{data.number}</button>
  6. }
  7. Child = React.memo(Child) // 传入函数组件,返回一个纯组件, 也是一个函数
  8. /*
  9. function memo(OldFunctionComponent){
  10. return class extends React.PureComponent{
  11. return <OldFunctionComponent {...this.props}/>
  12. }
  13. }
  14. */
  15. // let MyData = {number:0};
  16. // let lastData;
  17. function App(){
  18. const [number,setNumber] = React.useState(0)
  19. const [name,setName] =React.useState('zhufeng')
  20. // 每次渲染app都会形成一个新对象,memo是浅比较,会比较地址
  21. // let data ={number}
  22. // 改成memo 1 参数是生成对象的工厂, 2参数是依赖的变量
  23. let data = React.useMemo(()=>({number}),[number])
  24. console.log(data === lastData)
  25. lastData = data
  26. // 每次渲染App都声明了一个新的函数
  27. // let addClick =()=> setNumber(number+1)
  28. // 改成useCallback优化
  29. let addClick =React.useCallback(()=> setNumber(number+1),[number])
  30. return(
  31. <div>
  32. <input value={name} onChange={e=>setName(e.target.value)}/>
  33. <Child data={data} onButtonClick={addClick}/>
  34. </div>
  35. )
  36. }
  37. function render(){
  38. ReactDOM.render(
  39. <Counter/>,
  40. document.getElementById('root')
  41. )
  42. }
  43. render();

2、 实现use

UseMemo

  1. function useMemo(factory, dependencies) {
  2. if (hookStates[hookIndex]) {
  3. // 说明不是第一次
  4. let [lastMemo, lastDependencies] = hookStates[hookIndex];
  5. // 判断一下新的依赖数组中的每一项是否跟上一次完全相等
  6. let same = dependencies.every(
  7. (item, index) => item === lastDependencies[index]
  8. );
  9. if (same) {
  10. hookIndex++;
  11. return lastMemo;
  12. } else {
  13. // 只要一个依赖变量不一样的话
  14. let newMemo = factory();
  15. hookStates[hookIndex++] = [newMemo, dependencies];
  16. return newMemo;
  17. }
  18. } else {
  19. // 说明是第一次渲染
  20. let newMemo = factory();
  21. hookStates[hookIndex++] = [newMemo, dependencies];
  22. return newMemo;
  23. }
  24. }
  25. // ... 使用自己的useState
  26. // ... 验证 使用了useMemo后, name发生变化的时候,data不变, data 和 lastData的引用地址相同

useCallback

  1. function useCallback(callback,dependencies){
  2. if(hookStates[hookIndex]){ // 说明不是第一次
  3. let [lastCallback,lastDependencies] = hookStates[hookIndex]
  4. // 判断一下新的一览数组中的每一项是否跟上一次完全相等
  5. let same = dependencies.every((item,index)=> item === lastDependencies[index])
  6. if(same){
  7. hookIndex++;
  8. return lastCallback
  9. }else{ // 只要一个依赖变量不一样的话
  10. hookStates[hookIndex++] = [callback,dependencies];
  11. return callback;
  12. }
  13. }else{ // 说明是第一次
  14. hookStates[hookIndex++] = [callback,dependencies]
  15. return callback
  16. }
  17. }
  18. // ... 使用自己的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、例子:

image.png

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对比