react的hook总结

2/12/2022 React

# react hook

组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来

  • useState【维护状态】
  • useEffect【完成副作用操作】
  • useContext【使用共享状态】
  • useReducer【类似redux】
  • useCallback【缓存函数】
  • useMemo【缓存值】
  • useRef【访问DOM】
  • useImperativeHandle【使用子组件暴露的值/方法】
  • useLayoutEffect【完成副作用操作,会阻塞浏览器绘制】

# useState()

const [name, setName] = useState(initState) 使用 useState 定义 state 变量时候,返回一个有两个值的数组。第一个值是当前的 state,第二个值是更新 state 的函数。

import React, { useState } from 'react'
import { Button } from 'antd'
const Home: React.FC<Iprops> = ({ dispatch, goodsList }) => {
  const [info, setInfo] = useState('init info')
  return (
    <div>
      <p>{info}</p>
      <Button onClick={() => setinfo('改变info')}> 点击更改info</Button>
    </div>
  )
}
export default Home
1
2
3
4
5
6
7
8
9
10
11
12

# useEffect -- 副作用钩子

useEffet 合成了calss组件中的componentDidMount, componentDidUpdate, componentWillUnmount 这三个生命周期

# useContext():共享状态钩子

Context :在组件之间共享值,不必显式地通过组件树的逐层传递 props。类似于 React 中Context Api 和 Vue 中的 provide/inject Api

使用context的子组件直接使用React.useContext(Context)就可获得context,而在Context Api中需使用<Consumer>({vlaue} => {})</Consumer>;父组件Provider写法不变。

const obj = { value: 1 };
const obj2 = { value: 2 };
const ObjContext = React.createContext(obj);
const Obj2Context = React.createContext(obj2);

const App = () => { return (
  <ObjContext.Provider value={obj}>
  <Obj2Context.Provider value={obj2}> <ChildComp /> </Obj2Context.Provider>
  </ObjContext.Provider>
);};
// 子级
const ChildComp = () => { return <ChildChildComp />; };
// 孙级或更多级
const ChildChildComp = () => {
  const obj = useContext(ObjContext);
  const obj2 = useContext(Obj2Context);
  return ( <><div>{obj.value}</div><div>{obj2.value}</div></> );
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# useReducer

# useMemo -- 缓存值

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])
//  不管页面 render 几次,时间戳都不会被改变,因为已经被被缓存了,除非依赖改变
const getNumUseMemo = useMemo(() => `${+new Date()}`, [])
1
2
3

在a和b的变量值不变的情况下,memoizedValue的值不变,useMemo函数的第一个入参函数不会被执行,节省计算量(像vue的计算属性)

import React, { useState } from 'react'
import { Input } from 'antd'
import Son1 from './son1'
interface Iprops {}
const Home: React.FC<Iprops> = () => {
  const [info, setInfo] = useState('')
  const [visible, setVisible] = useState(true)
  // const onVisible = () => { setVisible((visible) => !visible) }
  const onVisible = useMemo(() => {
    return () => { setVisible((visible) => !visible) }
  }, [])
  const changeInfo = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value
    setInfo(value)
  }
  return ( <div>
      <p>{info}</p>
      <Input onChange={(e) => changeInfo(e)}></Input>
      <Son1 onVisible={onVisible} />
    </div>)
}
export default Home
// 子组件
import React, { memo } from 'react'
import { Button } from 'antd'
interface Iprops { onVisible: () => void }
const Son1: React.FC<Iprops> = ({ onVisible }) => {
  console.log('我被重新渲染了....')
  return ( <Button onClick={() => onVisible()}>button</Button> )
}
// export default memo(Son1)  
export default Son1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

在父组件中Input输入框每次输入新的值,父组件的info的值就会发生改变,子组件每次都会重新渲染,即使子组件没用到info值,因为setInfo导致父组件重新渲染了,也导致onVisible每次都变成一个新的值,所以引起子组件重新渲染。

利用React.memo,props.onVisible是一个函数,它是一个引用类型的值,当父组件重新渲染onVisible 这个函数也会重新生成,这样引用地址变化就导致对比出新的数据,子组件就会重新渲染。

# useCallback -- 缓存函数

// 除非 `a` 或 `b` 改变,否则不会变
const memoizedCallback = useCallback(() => doSomething(a, b),  [a, b],);
1
2

react中只要父组件的 render 了,那么默认情况下就会触发子组的render,避免这种重渲染方法: React.PureComponentReact.memoshouldComponentUpdate()

useMemo是缓存值,useCallback一个是缓存函数的引用。也就是说 useCallback(fn, [deps]) 相当于 useMemo(() => fn, [deps])

const Home: React.FC<Iprops> = () => {
  const [info, setInfo] = useState('')
  const [visible, setVisible] = useState(true)
  // const onVisible = useMemo(() => { return () => setVisible((visible) => !visible) }, [])
  const onVisible = useCallback(() => { setVisible(visible => !visible)}, [])
  const changeInfo = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value
    setInfo(value)
  }
  return (<div>
      <p>{info}</p>
      <Input onChange={(e) => changeInfo(e)}></Input>
      <Son1 onVisible={onVisible} />
    </div>)
}
export default Home
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# useRef

  • 判断是否是由于页面更新而非首次渲染
  • 获取 Dom 元素,在函数组件中通过useRef 来获取对应的 Dom 元素,拿到子组件的实例,相当于class组件的React.createRef()
import { useRef } from 'react';
export function useFirstMountState(): boolean {
  const isFirst = useRef(true);
  if (isFirst.current) {
    isFirst.current = false;
    return true;
  }
  return isFirst.current;
}
1
2
3
4
5
6
7
8
9

访问DOM,从而操作DOM,如点击按钮聚焦文本框

const Index = () => {
    const inputEl = useRef(null);
    const handleFocus = () => { inputEl.current.focus(); };
    return (<>
        <input ref={inputEl} type="text" />
        <button onClick={handleFocus}>Focus</button>
    </>);
};
1
2
3
4
5
6
7
8

要访问的是一个组件,操作组件里的具体DOM----React.forwardRef 高阶组件来转发ref

const Index = () => {
  const inputEl = useRef(null);
  const handleFocus = () => { inputEl.current.focus(); };
  return ( <>
    <Child ref={inputEl} />
    <button onClick={handleFocus}>Focus</button>
  </>);
};
const Child = forwardRef((props, ref) => {
  return <input ref={ref} />;
});
1
2
3
4
5
6
7
8
9
10
11

# 自定义 hook