0
点赞
收藏
分享

微信扫一扫

React Hook

E_topia 2021-09-19 阅读 62

react 16.8 以后加上了 react hook,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。16.8之前,react组件可以分为类组件和函数组件。

  • 函数组件一定是无状态组件,展示型组件(渲染组件)一般是无状态组件;
  • 类组件既可以是有状态组件,又可以是无状态组件。类组件也可以叫容器组件,一般有交互逻辑和业务逻辑,而容器型组件一般是有状态组件。

我们为什么要拥抱react hook?由于类组件存在以下几点问题:

  • 组件变得复杂和难以维护,业务变得复杂之后,组件之间共享状态变得频繁,此时组件将变得非常难以理解和维护,复用状态逻辑更是难上加难。
  • 满天class导致的热重载和性能问题,class自生具有的复杂度和组件嵌套过深props层级传递。
  • 函数式组件没有状态

下面逐一介绍官方提供的hook API。
1.useState()
作用:返回一个状态以及能修改这个状态的setter,在其他语言称为元组(tuple),一旦mount之后只能通过这个setter修改这个状态。


2.useEffect(callback, arr)
作用:处理函数组件中的副作用,如异步操作、延迟操作等。useEffect有两个参数,callback和数组依赖项,无arr时相当于componentDidMount生命周期,有arr时相当componentDidMount和componentDidUpdata生命周期。如果callback中有return,则相当于componentWillUnmount。

3.useContext
作用:跨组件共享数据钩子,使用可分为三步:

  • 首先使用React.createContext API创建Context,由于支持在组件外部调用,因此可以实现状态共享
export const MyContext = React.createContext(null);
  • 使用Context.Provider API在上层组件挂载状态
<MyContext.Provider value={value}>
  <childComponent />
</MyContext.Provider>
  • 获取上层组件中距离当前组件最近的<MyContext.Provider> 的 value
const value = useContext(MyContext)   // MyContext 为 context 对象(React.createContext 的返回值) 

4.useReducer

作用:用于管理复杂的数据结构(useState一般用于管理扁平结构的状态),基本实现了redux的核心功能。useState的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。

const initialState = {count: 0};
const reducer = (state, action)=> {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      return state;
  }
}
const [state, dispatch] = useReducer(reducer, initialState);
return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );

5.useMemo、useCallback
这俩个Api与性能优化有关。react中,性能的优化点在于:

  • 调用setState,就会触发组件的重新渲染,无论前后的state是否不同
  • 父组件更新,子组件也会自动的更新

基于上面的两点,我们通常的解决方案是:使用immutable进行比较,在不相等的时候调用setState;在shouldComponentUpdate中判断前后的props和state,如果没有变化,则返回false来阻止更新。
在hooks出来之后,我们能够使用function的形式来创建包含内部state的组件。但是,使用function的形式,失去了上面的shouldComponentUpdate,我们无法通过判断前后状态来决定是否更新。而且,在函数组件中,react不再区分mount和update两个状态,这意味着函数组件的每一次调用都会执行其内部的所有逻辑,那么会带来较大的性能损耗。因此useMemo 和useCallback就是解决性能问题的杀手锏。

useCallback和useMemo的参数跟useEffect一致。useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数。

通过一个例子来看useMemo的作用:

const [count, setCount] = useState(1);
const [val, setValue] = useState('');
const expensive = () => {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
}
return <>
        <h4>{count}-{val}-{expensive()}</h4>
        <div>
            <button onClick={() => setCount(count + 1)}>+c1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
</>;

不使用useMemo,无论count还是val改变都会执行expensive()

const [count, setCount] = useState(1);
const [val, setValue] = useState('');
const expensive = useMemo(() => {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
}, [count])
return <>
        <h4>{count}-{val}-{expensive()}</h4>
        <div>
            <button onClick={() => setCount(count + 1)}>+c1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
</>;

使用useMemo,只有在count发生改变使才会会执行expensive()

useCallback跟useMemo比较类似,但是使用场景不同:比如有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

const [count, setCount] = useState(1);
const [val, setVal] = useState('');

const callback = useCallback(() => {
    console.log('callback执行');
    return count;
}, [count]);

return <>
    <h4>{count}</h4>
    <Child callback={callback} />
    <div>
        <button onClick={() => setCount(count + 1)}>+</button>
        <input value={val} onChange={event => setVal(event.target.value)} />
    </div>
</>

const Child = (props: any) => {
    const [count, setCount] = useState(() => props.callback());

    useEffect(() => {
        setCount(props.callback());
    }, [props.callback]);

    return <div>{count}</div>;
};

6.useRef

useRef是一个方法,返回一个可变的 ref 对象;其 .current 属性被初始化为传入的参数(initialValue);可以保存任何类型的值:dom、对象等任何可变值;返回的 ref 对象在组件的整个生命周期内保持不变;修改 ref 的值是不会引发组件的重新 render 。

useRef非常常用的一个操作,访问DOM节点,对DOM进行操作,监听事件等等,如下:

const inputEl = useRef(null);
const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
};
return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
);

除了传统的用法之外,它还可以“跨渲染周期”保存数据。

const likeRef = useRef(1);
const [like, setLike] = useState(1);
const onButtonClick = () => {
    setTimeout(() => {
        console.log(likeRef.current); //11
        console.log(like); // 1
    }, 2000);
};
return (
  <>
    <button
        onClick={() => {
           likeRef.current++;
           setLike(like + 1);
    }}>
    {like}
    </button>
    <button onClick={onButtonClick}>打印like</button>
  </>
)

在上面的例子中,点击了打印like按钮后,连续点击数字按钮,会发现2s后likeRef.current打印出11,而like打印出1。

因为,在任意一次渲染中,props和 state 是始终保持不变的,如果props和state在任意不同渲染中是相互独立的话,那么使用到他们的任何值也是独立的。所以onButtonClick时拿到的时未点击数字按钮时的like值。
而ref 在所有 render 都保持着唯一的引用,因此所有的对 ref 的赋值或者取值拿到的都是一个最终的状态,而不会存在隔离。

7.useImperativeHandle

当userRef用于一个组件时,useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值;如果不使用,父组件的ref(chidlRef)访问不到任何值(childRef.current==null);且useImperativeHandle 应当与 forwardRef 一起使用。

const RefDemo= ()=>{
    const childRef = useRef<any>(null);
    const onButtonClick = () => {
        childRef.current?.say();
    };
    return (
    <div>
        <Child ref={childRef} />
        <button onClick={onButtonClick}>say123</button>
    </div>
    )
};
export default RefDemo;
const Child = React.forwardRef((props, ref) => {
    useImperativeHandle(ref, () => ({
        say: () => {
            console.log('123');
        },
    }));
    return (
        <>
            <h4>子组件</h4>
        </>
    );
});
  • React.forwardRef会创建一个React组件,这个组件能够将其接受的ref属性转发到其组件树下的另一个组件中。
  • React.forwardRef接受渲染函数作为参数,React将使用prop和ref作为参数来调用此函数。

8.useLayoutEffect
用法与useEffect 相同,但它会在所有的 DOM 变更之后同步调用(立即执行)。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。由于会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制。
使用场景:当useEffect里面的操作需要处理DOM,并且会改变页面的样式,就需要用这个,否则可能会出现闪屏问题。

9.useDebugValue
useDebugValue 用于在 React 开发者工具中显示 自定义 Hook 的标签。
useDebugValue 接受一个格式化函数作为可选的第二个参数。该函数只有在 Hook 被检查时才会被调用。它接受 debug 值作为参数,并且会返回一个格式化的显示值。

举报

相关推荐

0 条评论