zoukankan      html  css  js  c++  java
  • React hooks解析(useState、useEffect、userReducer、useCallback、useMemo、userContext、useRef)

    什么是Hooks?

    'Hooks'的单词意思为“钩子”。React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。而React Hooks 就是我们所说的“钩子”。

    常用的钩子

    useState()
    useEffect()
    userReducer()
    useCallback()
    useMemo()
    useContext()
    useRef()

    一、userState():状态钩子

    纯函数组件没有状态,useState()用于为函数组件引入状态。在useState()中,数组第一项为一个变量,指向状态的当前值。类似this.state,第二项是一个函数,用来更新状态,类似setState。

    import React, {useState} from 'react'
    const AddCount = () => {
      const [ count, setCount ] = useState(0)
      return (
        <div>
          <button onClick={()=>setCount(count++)}>加一</button>
        </>
      )
    }
    export default AddCount 

    二、useEffect():副作用钩子

    useEffect()接受两个参数,第一个参数是你要进行的异步操作,第二个参数是一个数组,用来给出Effect的依赖项。只要这个数组发生变化,useEffect()就会执行

    useEffect()可以看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

    useEffect(
      () => { 
        const subscription = props.source.subscribe();
        return () => {//相当于ComponentWillUnmount
          subscription.unsubscribe();
        };
      },
      [props.source],//相当于ComponentDidUpdate
    );

    三、useReducer():Action钩子

    我们通过用户在页面中发起action,从而通过reducer方法来改变state,从而实现页面和状态的通信。
    const [state, dispatch] = useReducer(reducer, initialState)

    它接受reducer函数和状态的初始值作为参数,返回一个数组,其中第一项为当前的状态值,第二项为发送action的dispatch函数。下面我们依然用来实现一个计数器。

    const reduer = (state, action) => {
        switch(action) {
            case 'add': return state + 1;
            case 'reduce': return state - 1;
            case 'reset': return 0;
            default:return state;
      }
    }

    函数组件:

    import React,{useReducer} from 'react'
    export default function Counter() {
         const [counter, dispatch] = useReducer(reduer, 0);
    }
    return (
        <div >
              <div>{counter}</div>
              <Button onClick={() => dispatch('add')}>递增</Button>
              <Button onClick={() => dispatch('reduce')}>递减</Button>
              <Button onClick={() => dispatch('reset')}>重置</Button>
        </div>
      );
    }

    useState是useReducer的一个子集,useState 返回的函数内部封装了一个 dispatch。useReducer( 单个组件中用的少,太重了)

    官方的定义:在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值(注意且字),或者下一个 state 依赖于之前的 state 等。

    四、useCallback和useMemo

    useMemo 和 useCallback 接收的参数都是一样,第一个参数为回调,第二个参数为要依赖的数据

    共同作用:仅仅依赖数据发生变化, 才会调用,也就是起到缓存的作用。useCallback缓存函数,useMemo 缓存返回值。

    useCallback使用场景

    有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;所有依赖本地状态或props来创建函数,需要使用到缓存函数的地方,都是useCallback的应用场景。

    父组件:

    import React, { useCallback } from 'react'
    
    function ParentComp () {
      // ...
      const [ name, setName ] = useState('hi~')
      // 每次父组件渲染,返回的是同一个函数引用
      const changeName = useCallback((newName) => setName(newName), [])  
      return (
        <div>
          <button onClick={increment}>点击次数:{count}</button>
          <ChildComp name={name} onClick={changeName}/>
        </div>
      );
    }

    子组件

    import React, { memo } from 'react'
    
    const ChildComp = memo(function ({ name, onClick }) {
      console.log('render child-comp ...')
      return <>
        <div>Child Comp ... {name}</div>
        <button onClick={() => onClick('hello')}>改变 name 值</button>
      </>
    })

    点击父组件按钮,控制台不会打印子组件被渲染的信息了。

    究其原因:useCallback() 起到了缓存的作用,即便父组件渲染了,useCallback() 包裹的函数也不会重新生成,会返回上一次的函数引用。

    useMemo

    import React, { useCallback } from 'react'
    
    function ParentComp () {
      // ...
      const [ name, setName ] = useState('hi~')
      const [ age, setAge ] = useState(20)
      const changeName = useCallback((newName) => setName(newName), [])
      const info = { name, age }    // 复杂数据类型属性
    
      return (
        <div>
          <button onClick={increment}>点击次数:{count}</button>
          <ChildComp info={info} onClick={changeName}/>
        </div>
      );
    }

    父组件渲染,const info = { name, age } 一行会重新生成一个新对象,导致传递给子组件的 info 属性值变化,进而导致子组件重新渲染。

    function ParentComp () {
      // ....
      const [ name, setName ] = useState('hi~')
      const [ age, setAge ] = useState(20)
      const changeName = useCallback((newName) => setName(newName), [])
      const info = useMemo(() => ({ name, age }), [name, age])   // 包一层
    
      return (
        <div>
          <button onClick={increment}>点击次数:{count}</button>
          <ChildComp info={info} onClick={changeName}/>
        </div>
      );
    }

    点击父组件按钮,控制台中不再打印子组件被渲染的信息了。

     五、useContext

    React的useContext应用场景:如果需要在组件A、B之间共享状态,可以使用useContext()。在它们的父组件上使用React的Context API,在组件外部建立一个Context。否则需要使用props一层层传递参数。

    import React,{ useContext } from 'react'
    const Ceshi = () => {
      const AppContext = React.createContext({})
      const A =() => {
        const { name } = useContext(AppContext)
        return (
            <p>A{name}</p>
        )
    }
    const B =() => {
      const { name } = useContext(AppContext)
      return (
          <p>B{name}</p>
      )
    }
      return (
        <AppContext.Provider value={{name: 'hook测试'}}>
        <A/>
        <B/>
        </AppContext.Provider>
      )
    }
    export default Ceshi 

    显示:Ahook测试,Bhook测试

    六、useRef

    只能为类组件定义ref属性,而不能为函数组件定义ref属性。想要在函数式组件中使用Ref,我们必须先了解两个Api,useRefforwardRef

    1、返回一个可变的ref对象,该对象只有个current属性,初始值为传入的参数(initialValue)。
    2、返回的ref对象在组件的整个生命周期内保持不变
    3、当更新current值时并不会re-render,这是与useState不同的地方
    4、更新useRef是side effect(副作用),所以一般写在useEffect或event handler里
    5、useRef类似于类组件的this
    6、每个组件的 ref 只跟组件本身有关,跟其他组件的 ref 没有关系
    import React, { useRef } from 'react'
    const LikeButton: React.FC = () => {
        let like = useRef(0)
        function handleAlertClick() {
            setTimeout(() => {
                alert(`you clicked on ${like.current}`)
            }, 3000)
        }
        return (
            <>
                <button
                    onClick={() => {like.current = like.current + 1}}>{like.current}赞</button>
                <button onClick=handleAlertClick}>Alert</button>
            </>
        )
    }
    export default LikeButton

    useRef与createRef的区别

    组件依赖的props以及state状态发生变更触发更新时,createRef每次都会返回个新的引用;而useRef不会随着组件的更新而重新创建。

    let refFromCreateRef = createRef()

    可以通过useRef传入子组件,调用子组件的方法。

    forwardRef: 将ref父类的ref作为参数传入函数式组件中

    const FancyButton = React.forwardRef((props, ref) => (  
      <button ref={ref} className="FancyButton">    
        {props.children}
      </button>
    ));
    // 可以直接获取到button的DOM节点
    const ref = React.useRef();
    <FancyButton ref={ref}>Click me!</FancyButton>;

    useImperativeHandle在函数式组件中,用于定义暴露给父组件的ref方法,用来限制子组件对外暴露的信息。

    只有useImperativeHandle第二个参数定义的属性跟方法可以在父组件能够获取到。

    function FancyInput(props, ref) {
      const inputRef = useRef();
      useImperativeHandle(ref, () => ({
        focus: () => {
          inputRef.current.focus();
        }
      }));
      return <input ref={inputRef} ... />;
    }
    FancyInput = forwardRef(FancyInput);
    //渲染 <FancyInput ref={inputRef} /> 的父组件
    //可以调用 inputRef.current.focus()

    参考:

    https://www.jianshu.com/p/d600f749bb19

    https://www.jianshu.com/p/014ee0ebe959

    https://blog.csdn.net/u011705725/article/details/115634265

  • 相关阅读:
    Cocos Creator代码编辑环境配置
    CocosCreator编辑器界面
    Colored Sticks (并查集+Trie + 欧拉路)
    子序列 NYOJ (尺取法+队列+hash) (尺取法+离散化)
    相同的雪花 Hash
    F
    逆序数
    士兵杀敌5 前缀数组
    Color the ball 线段树 区间更新但点查询
    士兵杀敌(二) 线段树
  • 原文地址:https://www.cnblogs.com/liangtao999/p/14703705.html
Copyright © 2011-2022 走看看