React-Hooks

215 阅读6分钟

什么是React-Hooks

你知道React-Hooks吗,说一下什么是React-Hooks?
  • React-Hooks是一种函数组件,可以在函数中使用state以及React的一些其他特性;
  • Hooks是React 16.8之后新增的React特性,内置了很多useState、useContext、useRef、useEffect等多种特性;当然,也可以根据自己的需求,自定义Hooks,要以use开头,并且在函数中,包含原有的hooks函数就可以了;

Hooks解决的问题

  • 类组件的不足
    • 类组件生命周期状态复杂,不利用复用
    • 对于一个逻辑在不同的生命周期中都有,导致代码分散,不易于维护
    • this指向问题
  • Hooks的优势
    • 去除了类组件的生命周期概念,更利于复用
    • 副作用关注点分离,可能更专注于逻辑

Hooks的使用方法

useState使用方法

  • 为什么使用useState?
    • useState对应类组件中的setState,是为使用state使用的方法
  • 具体使用方法
    // 这里可以任意命名,因为返回的是数组,数组解构
    const [state, setState] = useState(initialState);
    
  • 注意事项
    • useState多次调用都是相互独立的
    • useState会返回一个数组,一个是state值,一个是setState的操作函数,通过函数解构的方式获取,setState函数不会把新的 state 和旧的 state 进行合并,而是直接替换
    • useState的初始值只会执行一次

useReducer使用方法

  • 为什么使用useReducer?

    • useReducer 和 redux 中 reducer 很像
    • useState 内部就是靠 useReducer 来实现的
    • useState 的替代方案,它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法
    • 在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等
  • 使用方法

    let initialState = 0;
    // 如果你希望初始状态是一个{number:0}
    // 可以在第三个参数中传递一个这样的函数 ()=>({number:initialState})
    // 这个函数是一个惰性初始化函数,可以用来进行复杂的计算,然后返回最终的 initialState
    const [state, dispatch] = useReducer(reducer, initialState, init);
    
    • 举个例子
    const initialState = 0;
    function reducer(state, action) {
      switch (action.type) {
        case 'increment':
          return {number: state.number + 1};
        case 'decrement':
          return {number: state.number - 1};
        default:
          throw new Error();
      }
    }
    function init(initialState){
        return {number:initialState};
    }
    function Counter(){
        const [state, dispatch] = useReducer(reducer, initialState,init);
        return (
            <>
              Count: {state.number}
              <button onClick={() => dispatch({type: 'increment'})}>+</button>
              <button onClick={() => dispatch({type: 'decrement'})}>-</button>
            </>
        )
    }
    
    
    
    
  • 注意事项

    • 暂无

useContext使用方法

  • 为什么使用useContext?

    • 跟class类组件中的context相对应,useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer>
  • 使用方法

    import React, {useContext, useReducer} from 'react'
    
    const reducer = (state = 0, {type}) => {
        switch (type) {
            case "add":
                return state + 1
            case 'delete':
                return state - 1
            default:
                return state;
        }
    }
    const Context = React.createContext(null);
    
    const Child = () => {
        const [count, dispatch] = useContext(Context)
        return (
            <div>
                <div>child...{count}</div>
                <button onClick={() => dispatch({type: 'add'})}>child add</button>
                <button onClick={() => dispatch({type: 'delete'})}>child delete</button>
            </div>
    
        )
    }
    
    const Hook = () => {
        const [count, dispatch] = useReducer(reducer, 10)
        return (
            <Context.Provider value={[count, dispatch]}>
                <div>
                    <div>mom ... {count}</div>
                    <Child/>
                    <button onClick={() => dispatch({type: 'add'})}>mom add</button>
                    <button onClick={() => dispatch({type: 'delete'})}>mom delete</button>
                </div>
            </Context.Provider>
        )
    }
    
    export default Hook
    
    
    
  • 注意事项

    • useContext接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值
    • 当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定 当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值
    • useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context

useEffect使用方法

  • 为什么使用useEffect?
    • useEffect对应的是class组件中的部分生命周期,分别为componentDidMount、componentDidUpdate、componentWillUnmount;
  • 使用方法
    • 比如在componentDidMount中,发送异步请求,模拟执行一次,第二个参数设置空数组即可
    useEffect(()=>{
        const users = 获取全国人民的信息()
    },[])
    
    • 每次更新的时候,都会执行的副作用函数,比如componentDidUpdate
    useEffect(()=>{
        const users = 每次都获取全国人民的信息()
    })
    
    • 可以设置在某些条件下才执行,比如,当text参数变化的时候
    useEffect(()=>{
        const users = 每次都获取全国人民的信息()
    }, [text])
    
    • 当useEffect的回调函数需要进行消除副作用时,即componentWillUnmount,可以直接返回消除副作用函数
    useEffect(() => {
        const subscription = 订阅全国人民吃饭的情报!
        return () => {
            取消订阅全国人民吃饭的情报!
        }
    },[])
    
    
  • 注意事项
    • effect(副作用):指那些没有发生在数据向视图转换过程中的逻辑,如 ajax 请求、访问原生dom 元素、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器、记录日志等。
    • 副作用操作可以分两类:需要清除的和不需要清除的。
    • useEffect 接收一个函数,该函数会在组件渲染到屏幕之后才执行,该函数有要求:要么返回一个能清除副作用的函数,要么就不返回任何内容

useRef使用方法

  • 为什么使用useRef?
    • useRef相当用class组件中的React.createRef
  • 使用方法
    • 相当于全局变量,一处被修改,其他地方都更新
    const [count, setCount] = useState(0)
    const countRef = useRef(0)
    useEffect(() => {
        console.log('use effect...',count)
        const timer = setInterval(() => {
            console.log('timer...count:', countRef.current)
            setCount(++countRef.current)
        }, 1000)
        return ()=> clearInterval(timer)
    },[])
    
    • 普遍操作,用来更新DOM
     const btnRef = useRef(null)
    
  • 注意事项

自定义Hooks组件

  • 一定要以use开头

经常使用的优化方式

优化的本质就是减少render的次数,如何减少组件的render的次数?

当数据没有变化的时候,即使父组件更新,也应该避免子组件的更新
  • Object.is 浅比较

    • Hook 内部使用 Object.is 来比较新/旧 state 是否相等
  • 优化方法

    • 使用 React.memo ,将函数组件传递给 memo 之后,就会返回一个新的组件,新组件的功能:如果接受到的属性不变,则不重新渲染函数;
    • useCallback:接收一个内联回调函数参数和一个依赖项数组(子组件依赖父组件的状态,即子组件会使用到父组件的值) ,useCallback 会返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新
    • useMemo:把创建函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算
  • 举个🌰

import React,{useState,memo,useMemo,useCallback} from 'react';

function SubCounter({onClick,data}){
    console.log('SubCounter render');
    return (
        <button onClick={onClick}>{data.number}</button>
    )
}
SubCounter = memo(SubCounter);

let oldData,oldAddClick;
export  default  function Counter2(){
    console.log('Counter render');
    const [name,setName]= useState('计数器');
    const [number,setNumber] = useState(0);
    // 父组件更新时,这里的变量和函数每次都会重新创建,那么子组件接受到的属性每次都会认为是新的
    // 所以子组件也会随之更新,这时候可以用到 useMemo
    // 有没有后面的依赖项数组很重要,否则还是会重新渲染
    // 如果后面的依赖项数组没有值的话,即使父组件的 number 值改变了,子组件也不会去更新
    //const data = useMemo(()=>({number}),[]);
    const data = useMemo(()=>({number}),[number]);
    console.log('data===oldData ',data===oldData);
    oldData = data;
    
    // 有没有后面的依赖项数组很重要,否则还是会重新渲染
    const addClick = useCallback(()=>{
        setNumber(number+1);
    },[number]);
    console.log('addClick===oldAddClick ',addClick===oldAddClick);
    oldAddClick=addClick;
    return (
        <>
            <input type="text" value={name} onChange={(e)=>setName(e.target.value)}/>
            <SubCounter data={data} onClick={addClick}/>
        </>
    )
}

参考链接