zoukankan      html  css  js  c++  java
  • React Hook

    React Hook是React16.8.0引入的。使可以在不引入class的情况下,可以使用state和其他React特性。

    hooks本质上是一些函数。

    1. 为什么引入Hook?

    1. hooks中的useEffect可以解决class中各逻辑在生命周期函数中管理混乱的问题。

    2.hooks中的自定义Hook使得可以不修改组件的结构的基础上,灵活的复用组件逻辑。

    3.class组件不能很好的压缩,并且热重载不稳定。不利于组件优化。使用Hook的函数组件不存在这些问题

    2. Hook的规则

    1. 只能在函数组件中使用hooks

        类组件中无效。

    2. 只能在函数最外层使用

       不能用于if,for等语句中,也不能用于普通js函数中。因为ReactHook通过调用顺序确定对应的state等对应的hook方法。使用语句等会改变顺序。

    3. 只能在以名字use开头的自定义Hook中使用

    3. useState

    接受一个参数作为初始值,参数可以是常量,也可以是一个返回值的函数。

    初始值如果是一个函数,在初次渲染执行;如果是一个函数的执行,每次渲染都会执行

    //  初始值是一个函数
    const [count, setCount] = useState(function init(){......;return ..})
    // 初始值是一个函数调用
    const [count, setCount] = useState(init())

    以数组形式返回两个值,第一个是状态值(初次渲染创建变量),一个是改变状态的函数。

    例如:

    const [count, setCount] = useState(0);
    // 0是初始值

    修改状态的函数(如setCount)和class中的setState类似,可以接受两种参数:

    setCount(fn/exp)

    1)表达式

    可以是常量, 也可以是一个带state值的表达式

    <button onClick={() => setCount(0)}>Reset</button>
    <button onClick={() => setCount(count+1)}>-</button>

    2)函数

    <button onClick={() => setCount(prevCount => prevCount + 1)}>-</button>

    ⚠️如果修改后的状态不变,重复调用只会刷新一次。

    <button onClick={() => setCount(count)}>+</button>
    <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button> 
    <!-- 每次单击第一个按钮会渲染一次,继续单击不继续渲染;
           但是如果再单击第二个按钮,切换到第一个按钮,还是会渲染一次-->

    模拟getDerivedStateFromProps

    在render前进行setState更新

    应用:在异步函数中获取state的值

    在setTimeout等异步函数中获取的状态值,是调用setTimeout方法时的状态值,不是执行时的状态值。

    function Example() {
      const [count, setCount] = useState(0);
    
      function handleAlertClick() {
        // count的取值形成了一个Example的闭包,每次刷新都是一个新的闭包
        setTimeout(() => {
          alert('You clicked on: ' + count);
        }, 3000);
      }
      
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>Click me</button>
          <button onClick={handleAlertClick}>Show alert</button>
        </div>
      );
    }
    ReactDOM.render(<Example/>, window.root);

    4. useEffect

    它相当于是componentDidMount,componentDidUpdate,componentWillUnMount三个生命周期的合成体。

    它接受两个参数, 第一个是一个函数(effect),第二个参数可选,是一个数组(第一个函数中用到的可变数据集合)。

    useEffect(() => {
      document.title = `You clicked ${count} times`;
    }, [count]); // 仅在 count 更改时更新;每次渲染,第一个函数都相当于一个新生成的函数

    1. 执行时机

    和生命周期不同的是,componentDidMount,componentDidUpdate是DOM渲染完成,屏幕更新前触发执行,会阻碍屏幕渲染;useEffect中的effect函数(非变更DOM的操作)在浏览器完成布局和绘制后,会延迟执行(异步,但是肯定在下一次渲染前执行),不会阻碍屏幕更新。但是如果是变更DOM的操作,需要同步执行。

    1) 如果只在初次加载的时候运行,模拟componentDIdMount

      useEffect(() => { 
        //TODO
      }, []);

    2)想要只在更新的时候运行,模拟componentDidUpdata

    //使用useRef()存储的实例变量作为是否是第一次执行的标识
      const [count, setCount] = useState(0);
      const first = useRef(true);
      useEffect(() => { 
        if (first.current) {
          first.current = false;
        } else {
          console.error(count);
          document.title = `You clicked ${count} times!`;
        }
      }, [count]);

    2. 执行副作用

    如果副作用需要取消,在传入useEffect的函数中返回一个函数,在返回的函数中执行取消操作,该返回函数会在组件卸载的时候执行。

     useEffect(() => {
      function handleStatusChange(status) {
        setIsOnline(status.isOnline);
      }
    
      ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
      return () => {
        ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
      };
    }, [props.friend.id]); // 仅在 props.friend.id 发生变化时,重新订阅

    3. 性能优化

    第二个参数传入的变量,会作为比较的依据。如果变量值不变,就会跳过本次副作用的执行。

    作为参数的值是在组件域(即函数组件对应的函数域)中的可变量(props/state),而且在useEffect中使用。

    5. useContext

    使函数组件可以具有和Class组件中的contextType属性(使可以通过this.context访问值)一样的功能。

    用法和Class中contextType基本一致。接受一个context对象作为参数,返回最近的Provider提供的值。

    示例代码:

    const ThemeContext = React.createContext('dark');
    function App() {
      const [theme, setTheme] = useState('dark');
      return (
        <ThemeContext.Provider value={theme}>
          <Child />
          <button onClick={() => setTheme(preTheme => preTheme === 'dark' ? 'light' : 'dark')}>切换主题</button>
        </ThemeContext.Provider>
      );
    }
    function Child() {
      const value = useContext(ThemeContext);
      return (
        <div>{value}</div>
      );
    }

    6.useReducer

    可以看做useState的复合版。当应用中需要多个状态时,一般需要调用多次useState(便于组合逻辑)。随着应用逐渐扩展,会越来越复杂,此时可以使用useReducer。

    const [state, dispatch] = useReducer(reducer, initialArg, init);
    // 第三个值可以不传,是一个接受第二个参数,返回一个初始值的函数

    示例:

    import React, { useReducer } from 'react';
    import ReactDOM from 'react-dom';
    
    function App() {
      function reducer(state, action) {
        switch(action.type) {
        case 'add':
          return state + 1;
        case 'minuse':
          return state - 1;
        case 'reset':
          return 0;
        default: 
          throw new Error('error');
        }
      }
      const [count, dispatch] = useReducer(reducer, 0);
      return (
        <div>
          <p>You clicked {count} times!</p>
          <button onClick={() => dispatch({type: 'reset'})}>Reset</button>
          <button onClick={() => dispatch({type: 'add'})}>+</button>
          <button onClick={() => dispatch({type: 'minuse'})}>-</button>    
        </div>
      );
    }

    模拟forceUpdate()

    function Example() {
      const [count, forceUpdate] = useReducer(x=>x+1, 0);
      return (
        <div>
          <button onClick={() => forceUpdate()}>Click me</button>
        </div>
      );
    }

    7. useMemo

    本质上是memorization技术。用于优化在渲染阶段,计算数据的函数。

    它接受一个计算函数作为第一参数;第二个是个依赖项数组。

    返回一个值,该值是第一个计算函数的返回结果。

    const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

    示例:

    import ReactDOM from 'react-dom';
    import PropTypes from 'prop-types';
    import React, {useState, useMemo, useEffect} from 'react';
    
    function App() {
      const inputRef = React.createRef();
      const [data, setData] = useState([]);
      useEffect(() => {
        inputRef.current.value = null;
      });
      return ( 
        <div>
          <input type='text' ref={inputRef}/>
          <button onClick={() => setData(data.concat(inputRef.current.value))}>添加</button>
          <Child data={data}/>
        </div>
      );
    }
    function Child(props) {
      const [filterText, setFilterText] = useState('');
      const lists = useMemo(() => props.data.filter(i => i && i.includes(filterText)), [filterText, props]);
      return(
        <div>
          <input onChange={(e) => setFilterText(e.target.value)} />
          <ul>
            {
              lists.map((i,inx) => <li key={inx}>{i}</li>)
            }
          </ul>
        </div>
      );
    }
    Child.propTypes = {
      data: PropTypes.array
    };
    
    ReactDOM.render(<App/>, window.root);

    8. useCallback

    和 useMemo基本相同;

    不同点在于它返回一个函数,这个和'memorize-one'的功能相同。

    const memoizedCallback = useCallback(
      () => {
        doSomething(a, b);
      },
      [a, b],
    );

    代码示例:

    // 用useCallback替换上面useMemo的示例
      const listFilter = useCallback(() => props.data.filter(i => i && i.includes(filterText)), [filterText, props]);
      const lists = listFilter(filterText, props);

    该方法比useRef的优点是,因为返回一个函数,它更容易抽取逻辑,形成自定义Hook

    function MeasureDOM() {
      const [rect, measureRef] = useClientRec();
      return (
        <>
          <h1 ref={measureRef}>Hello, world</h1>
          {rect && <h2>The above header is {Math.round(rect.height)}px tall</h2>}
        </>
      );
    }
    function useClientRec() {
      const [rect, setRect] = useState();
      // 此时ref属性指向一个回调函数
      const ref = useCallback(node => {
        if (node !== null) {
          setRect(node.getBoundingClientRect());
        }
      }, []);
      return [rect, ref];
    }

    9 .useRef

    useRef模拟的是Class组件中的实例属性。可以在函数组件中很方便的保存任何可变值,不引起渲染。

    // initialValue是current属性的初始值
    const refContainer = useRef(initialValue);

    使用useRef和React.createRef的区别是:

    1)useRef返回一个refContainer对象({current: XXX}),并且重新渲染,访问的都是同一个对象。

    2)React.createRef在函数组件中是每次重新渲染,相当于重新创建了一个对象,每次都是一个新对象。

    示例:

    // 单击一次后,inputRef1为1, inputRef2仍然为0
    import React, {useState,useEffect, useRef} from 'react';
    import ReactDOM from 'react-dom';
    
    function App() {
      const [count, setCount] = useState(0);
      const inputRef1 = useRef(null);
      const inputRef2 = React.createRef();
      useEffect(() => {
        if(count === 1) {
          inputRef1.current.value = count;
          inputRef2.current.value = count;
        }
      }, [count,inputRef1,inputRef2]);
      return (
        <div>
          <input ref={inputRef1} /><br/>
          <input ref={inputRef2} /><br/>
          inputRef1:{inputRef1.current && inputRef1.current.value || 0}<br/>
          inputRef2:{inputRef2.current && inputRef2.current.value || 0}<br/>
          <button onClick={() => setCount(count+1)}>Add</button>
        </div>
      );
    }
    
    ReactDOM.render(<App/>, window.root);

    应用: 获取prevState或者prevProps

    // 考虑到通用性,将其提取到自定义Hook中usePrevious
    function App() {
      const [count, setCount] = useState(0);
      const previous = usePrevious(count);
      return (
        <div>
          <p>previous: {previous}</p>  
          <p>current: {count}</p>  
          <button onClick={() => {setCount(count+1);}}>Add</button>
        </div>
      );
    }
    // 自定义Hook-获取prevState/prevProps
    function usePrevious(value) {
      const ref = useRef();
      useEffect(() => {
        ref.current = value;
      });
      return ref.current;
    }

    10. useImperativeHandle

    该方法可以自定义(默认是DOM节点)子组件暴露给父组件的内容。

    useImperativeHandle(ref, createHandle, [deps])

    该方法基于Refs转发,需要和React.forwardRef结合使用。

    示例:

    import React, { useRef,useEffect, useImperativeHandle } from 'react';
    import ReactDOM from 'react-dom';
    
    function App() {
      const inputRef = useRef(null);
      useEffect(() => {
        // 在父组件查看获取到的子组件中传递的ref的内容
        console.log(inputRef); // {current: {focus: () => {...}}}
      });
      return (
        <div>
          <FancyInputWithRef ref={inputRef} />
        </div>
      );
    }
    // 子组件
    function FancyInput(props, ref) {
      // 自定义ref暴露的内容
      useImperativeHandle(ref, () => ({
        focus: () => ref.current.focus()
      }), [ref]);
      return(
        <div>
          <input ref={ref} />
        </div>
      );
    }
    // 转发ref
    var FancyInputWithRef = React.forwardRef(FancyInput);
    
    ReactDOM.render(<App/>, window.root);

    11. useLayoutEffect

    和useEffect功能基本相同。

    主要区别是它的执行时机和componentDidMount,componentDidUpdate相同,都在浏览器绘制之前执行。

    建议: 先使用useEffect, 有问题再用useLayoutEffect

    12. useDebugValue

    用于调试时在自定义Hook中添加标签。

    useDebugValue(date, date => date.toDateString());

    示例:

    function useFriendStatus(friendID) {
      const [isOnline, setIsOnline] = useState(null);
    
      // ...
    
      // 在开发者工具中的这个 Hook 旁边显示标签
      // e.g. "FriendStatus: Online"
      useDebugValue(isOnline ? 'Online' : 'Offline');
    
      return isOnline;
    }
  • 相关阅读:
    Kafka Producer 的缓冲池机制【转】
    【转】kafka如何实现每秒几十万的高并发写入?
    【转】zk和eureka的区别(CAP原则)
    【转】10倍请求压力来袭,你的系统会被击垮吗?
    (转发)深度学习模型压缩与加速理论与实战(一):模型剪枝
    Time Lens: Event-based Video Frame Interpolation
    PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation
    Self-Supervised Learning with Swin Transformers
    DashNet: A Hybrid Artificial and Spiking Neural Network for High-speed Object Tracking
    KAIST Multispectral Pedestrian Detection Benchmark
  • 原文地址:https://www.cnblogs.com/lyraLee/p/12000348.html
Copyright © 2011-2022 走看看