Hooks的实现原理及应用场景

205 阅读5分钟

作用

对函数型组件进行增强,让函数型组件可以存储状态,可以拥有处理副作用的能力.让开发者在不使用类组件的情况下,实现相同的功能.

  • 要解决的问题
  1. 类组件缺少逻辑复用机制 组件间如果有state的逻辑是相似的, cLass模式下基本上是用高阶组件来解决的.虽然能够解决问题,但是我们需要在组件外部再包一层元素,会导致层级非常冗余. 为了复用逻辑增加无实际渲染效果的组件,增加了组件层级显示十分臃肿增加了调试的难度以及运行效率的降低

  2. 类组件经常会变得很复杂难以维护 将一组相干的业务逻辑拆分到了多个生命周期函数中在一个生命周期函数内存在多个不相干的业务逻辑

  3. 类成员方法不能保证this指向的正确性

useState

  1. useState,用于为函数组件保存状态,让函数内的变量使用完不被释放内存
  2. 接收唯一的参数即状态初始值.初始值可以是任意数据类型.
  3. 返回值为数组.数组中存储状态值和更改状态值的方法.方法名称约定以set开头,后面加上状态名称,方法是异步的.
  4. 方法可以被调用多次.用以保存不同状态值.
  5. 参数可以是一个函数,用在初始值是动态值的情况,函数返回什么,初始状态就是什么,函数只会被调用一次.
import React, { useState } from 'react'
function App(props){
    const [count, setCount] = useState(()=>{
        return props.count || 0
    })
    return <div>
        <span>{count}</span>
        <button onClick={()=>setCount(count + 1)}>+1</button>
    </div>;
}
  1. 将异步变成同步
setCount(count => {
    const newCount = count + 1;
    document.title = newCount;
    return newCount;
})

useReducer

  1. 作用:当子组件要修改父组件状态时,可以不用将所有方法传递下来,直接传递dispath,子组件通过调用它可以触发任意一个action对状态进行修改
  2. 使用
import React, { useReducer } from 'react'
function reducer(state, action) {
    switch (action.type) {
        case 'add':
            return state + 1
        case ...
    }
}
function App(){
    const [count, dispatch] = useReducer(reducer, 0)
    return <div>
        <span>{count}</span>
        <button onClick={()=>dispatch({type: 'add'})}>+1</button>
    </div>;
}

useContext

  1. 作用
  • 在跨组件层级获取数据时简化获取数据的代码
  1. 不使用
import React, { createContext } from 'react';
const countContext = createContext();
function App() {
    return <countContext.Provider value={100}>
        <Foo />
    </countContext.Provider>
}

function Foo() {
    return <countContext.Consumer>
        {
            value => {
                return <div>{value}</div>
            }
        }
    </countContext.Consumer>
}

export default App;
  1. 使用
import React, { createContext, useContext} from 'react';
const countContext = createContext();
function App() {
    return <countContext.Provider value={100}>
        <Foo />
    </countContext.Provider>
}

function Foo() {
    const value = useContext(countContext)
    return <div>{value}</div>
}

export default App;

useEffect

  1. 作用:让组件拥有处理副作用的能力,类似生命周期函数
  2. 执行时机
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';

function App() {
    const [count, setCount] = useState(0);
    // 1、componentDidMount、componentDidUpdate
    useEffect(()=>{
        console.log(123)
    })
    // 2、componentDidMount
    useEffect(()=>{
        console.log(123)
    },[])
    // 3、componentWillUnMount
    useEffect(()=>{
        return ()=>{
            console.log(123)
        }
    })
    return (
    <div>
        <span>{count}</span>
        <button onClick={()=>setCount(count + 1)}>+1</button>
        <button onClick={()=>ReactDOM.unmountComponentAtNode(document.getElementById('root'))}>卸载组件</button>
    </div>)
}

ReactDOM.render(
    <App />,
    document.getElementById('root')
)
  1. 作用
  • 按照用途将代码进行分类(将不相干的业务逻辑归置到不同的副作用函数中)
  • 简化重复代码,使组件内部代码更加清晰(组件更新和组件挂载的操作写在同一个useEffect中)
  • 数据监测:第二个参数的数据发生变化才执行更新
  1. 异步操作
  • useEffect中的参数函数不能是异步函数,因为useEffect函数要返回清理资源的函数,如果是异步函数就变成了返回Promise
// 正确的实现方法
useEffect(()=>{
    (async ()=>{
        await axios.get()
    })()
})

useMemo

  1. 作用
  • useMemo 的行为类似Vue中的计算属性,可以监测某个值的变化,根据变化值计算新值.
  • useMemo 会缓存计算结果.如果监测值没有发生变化,即使组件重新渲染,也不会重新计算.此行为可以有助于避免在每个渲染上进行昂贵的计算
const result = useMemo(() => {
    // count发生改变此函数重新执行
    return result;
}, [count])
  1. momo
  • 性能优化,如果本组件中的数据没有发生变化,阻止组件更新.类似类组件中的 PureComponent 和 shouldComponentUpdate
import React, { useState, memo } from 'react'
function App(props){
    const [count, setCount] = useState(0)
    return <div>
        <span>{count}</span>
        <button onClick={()=>setCount(count + 1)}>+1</button>
        <Foo />
    </div>;
}
// count变化不会触发更新
const Foo = memo(function Foo() {
    return <div>Foo</div>
})

useCallback

  • 性能优化,缓存函数,使组件重新渲染时得到相同的函数实例
import React, { useState, useCallback } from 'react'
function App(){
    const [count, setCount] = useState(0)
    const resetCount = useCallback(()=> setCount(0), [setCount])
    return <div>
        <span>{count}</span>
        <button onClick={()=>setCount(count + 1)}>+1</button>
        <Foo resetCount = {resetCount} />
    </div>;
}

useRef

  1. 获取DOM元素对象
import React, { useRef } from 'react'
function App(){
    const box = useRef()
    return <div ref={box}>
        <button onClick={()=>console.log(box)}>获取DIV</button>
    </div>;
}
  1. 保存数据(跨组件周期) 即使组件重新渲染,保存的数据仍然还在.保存的数据被更改不会触发组件重新渲染
import React, { useState, useRef, useEffect } from 'react'
function App(){
    const [count, setCount] = useState(0)
    let timeID = useRef()
    useEffect(()=>{
        timeID.current = setInterval(() => {
            setCount(count + 1);
        },1000)
    },[])
    const stopCount = () => {
        clearInterval(timeID)
    }
    return <div>
        {count}
        <button onClick={stopCount}>停止</button>
    </div>;
}

自定义Hook

  • 是标准的封装和共享逻辑的方式
  • 是一个函数, 其名称以 use 开头
  • 其实就是逻辑和内置 Hook 的组合

路由Hooks

  • 获取路由信息
import { useHistory, useLocation, useRouteMatch, useParams } from 'react-router-dom'
function App(props){
    // 获取对象信息
    console.log(useHistory());
    console.log(useLocation());
    console.log(useRouteMatch());
    // 获取Match对象里面的路由参数
    console.log(useParams());
    return <div />
}

实现原理

  1. useState
import React from 'react';
import ReactDOM from 'react-dom';

const stateList = [];
const stateAcions = [];
let stateIndex = 0;

function useState(initState) {
    stateList[stateIndex] = stateList[stateIndex] ? stateList[stateIndex] : initState;
    stateAcions[stateIndex] = function (newState) {
        stateList[stateIndex] = newState;
        render()
    }
    const state = stateList[stateIndex];
    const setState = stateAcions[stateIndex];
    stateIndex++;
    return [state, setState]
}

function render() {
    stateIndex = 0;
    ReactDOM.render(<App />, document.getElementById('root'))
}
  1. useEffect
let preArray = [];
let effectIndex = 0;
function useEffect(callback, array){
    // 判断callback是不是函数
    if(Object.prototype.toString.call(callback) !== '[object Function]') throw new Error('第一个参数必须是函数')
    // 判断有无array
    if(typeof array === 'undefined'){
        callback()
    }else{
        // 判断array是不是数组
        if(Object.prototype.toString.call(array) !== '[object Array]') throw new Error('第二个参数必须是数组')
        // 判断数组依赖值是否变化
        let preItem = preArray[effectIndex]
        const hasChanged = preItem ? array.every((item, index)=> item === preItem[index]) === false : true
        if(hasChanged){
            callback()
        }
        // 同步依赖值
        preArray[effectIndex] = preItem
        effectIndex++
    }
} 

function render(){
    effectIndex = 0;
}
  1. useReducer
function useReducer (reducer, initState) {
    const [state, setState] = useState(initState);
    function dispatch (action) {
        const newState = reducer(state, action)
        setState(newState)
    }
    return [state, dispatch]
}