hooks详解

228 阅读4分钟

一、useState

  1. 函数组件的setState,不能局部更新
  2. setState(obj),如果obj地址没变那么React不会做更新
  3. useState可以写成函数和直接写数据没有区别
incrementCount() {
  this.setState({count: this.state.count + 1});
}

handleSomething() {
  this.incrementCount();
  this.incrementCount();
  this.incrementCount();
}

不要指望在调用 setState 之后,this.state 会立即映射为新的值。incrementCount()会等到handleSomething()执行完之后再去执行,此时setState是异步的

incrementCount() {
  this.setState((state) => {
    return {count: state.count + 1}
  });
}

handleSomething() {
  this.incrementCount();
  this.incrementCount();
  this.incrementCount();
}

如果传的是一个函数那么React会依次调用

二、useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);

useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

优点就在于把数据和配套的方法都聚拢在一起

接受参数:

dispatch({type: 'decrement',data:{date1:xxx}})
    case 'increment':
      return {count: state.count + 1 ,...action.data};

三、useContext

  • const xxx =  createContext(initialValue)
  • 使用xxx.Provider圈住范围

    <xxx.Provider value={xxx}>
      <Toolbar />
    </xxx.Provider>

  • const xxx = useContext(xxx);

四、插播-React的更新机制

React的更新不是响应式的,比如说我们setN之后,页面不会响应式的更新,而是通知组件重新render,来对比改变了什么。

同理Context也不是响应式的,兄弟组件之间修改context,会通知父组件重新渲染,另一个兄弟组件的值才会更新

五、useEffect

本质上是setN代码触发的re-render中再次调用了useEffect,根据第二个参数判断这次re-render结束后是否执行回调。

依然是根据顺序来决定数组(state数据)中的哪一项

useEffect(()=>{
return()=>{
   //在这里清除无用代码,防止内存泄漏,相当于destoryed
  }
})

存在多个钩子按顺序执行

六、useLayoutEffect

  • 其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
  • useEffect 是在在浏览器执行绘制之后执行。
  • 它会在浏览器执行绘制之前调用 effect
  • 尽可能使用标准的 useEffect 以避免阻塞视觉更新,用户就算看到更新过程也算看到了页面,用了layout可能让用户推迟几秒看到页面
  • useLayoutEffect先于useEffect
  • useLayoutEffect最好和操作dom有关

七、React.memo和useMemo

和类组件的pureCompontent类似,只不过只适用于函数组件,而不适用 class 组件。只要props不变就不重新执行函数组件。

但是添加监听函数后会出现bug,因为重新渲染App组件后,里面的函数重新生成,导致函数的地址改变了,使用相当于props变了。

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

把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。

相当于computed。

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

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

八、useRef

const count = useRef(0)

count.current 来读取

为什么需要.current因为,如果count只是一个普通值,那么复制的是值,对象的话复制的是值

改变count.current 并不会刷新UI,配合setN来刷新UI

九、Ref和forwardRef

类组件的ref

class AutoFocusTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();  }

  componentDidMount() {
    this.textInput.current.focusTextInput();  }

  render() {
    return (
      <CustomTextInput ref={this.textInput} />    );
  }
}

默认情况下,你不能在函数组件上使用 ref 属性,因为它们没有实例:如果要在函数组件中使用 ref,你可以使用 forwardRef

function CustomTextInput(props) {
  // 这里必须声明 textInput,这样 ref 才可以引用它
  const textInput = useRef(null);

  function handleClick() {
    textInput.current.focus();
  }

  return (
    <div>
      <input
        type="text"
        ref={textInput} />
      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );
}

一个函数组件,想要接受ref

  <button ref={ref} className="FancyButton">
    {props.children}
  </button>

必须通过forwardRef包装整个组件

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

我们就可以传进来了

<FancyButton ref={ref}>Click me!</FancyButton>;

前提是已经声明好ref了,React.createRef 创建一个能够通过 ref 属性附加到 React 元素的ref

const ref = React.useRef();
  1. 我们通过调用 React.createRef 创建了一个 React ref 并将其赋值给 ref 变量。
  2. 我们通过指定 ref 为 JSX 属性,将其向下传递给 <FancyButton ref={ref}>
  3. React 传递 refforwardRef 内函数 (props, ref) => ...,作为其第二个参数。
  4. 我们向下转发该 ref 参数到 <button ref={ref}>,将其指定为 JSX 属性。
  5. 当 ref 挂载完成,ref.current 将指向 <button> DOM 节点。

我们在子组件上写上ref属性时,子组件不认识ref属性,所以需要forwardRef

十、useRef 和 createRef 

useRef 仅能用在 FunctionComponent,createRef 仅能用在 ClassComponent。

第一句话是显然的,因为 Hooks 不能用在 ClassComponent。

第二句话的原因是,createRef 并没有 Hooks 的效果,其值会随着 FunctionComponent 重复执行而不断被初始化。

上面的forwardRef是指组件能不能接受ref,而不是普通dom能不能接受ref