React Hooks

2019-03-29  本文已影响0人  Oldboyyyy

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性

问题

react 现在面临的问题

渐进策略

没有计划从 React 中移除 class, 最重要的是,Hook 和现有代码可以同时工作,你可以渐进式地使用他们。

Hook 全家福

Basic Hooks

自定义 Hook

Additional Hooks

Hook 使用规则

Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:

npm install eslint-plugin-react-hooks
// 你的 ESLint 配置
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error", // 检查 Hook 的规则
    "react-hooks/exhaustive-deps": "warn" // 检查 effect 的依赖
  }
}

useState

通过在函数组件里调用它来给组件添加一些内部 stateReact 会在重复渲染时保留这个 state

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 “count” 的 state 变量。
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

useState 会返回一对值: 当前的状态和更新这个状态的函数。
更新状态的函数就类似 class 组件setState方法, 但是更新新状态的函数不会将新的 state旧的 state 合并, 而是直接替换旧的 state
入参 : 是作为 state 的初始值, 只在第一次渲染的时候用到。可以是 number, boolean, string, object 的值, 如果初始值需要而外的计算也可以是一个 function
出参 : [state, setState] state => 当前的状态; setState => 更改这个状态的函数。


useEffect

useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途,只不过被合并成了一个 API。

import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // 相当于 componentDidMount 和 componentDidUpdate:
  useEffect(() => {
    // 使用浏览器的 API 更新页面标题
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

当你调用 useEffect 时,就是在告诉 React 在完成对 DOM 的更改后运行你的“副作用”函数。由于副作用函数是在组件内声明的,所以它们可以访问到组件的 propsstate

useEffect 会在每次渲染后都执行吗?

是的, 默认情况下,React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候。 React 保证了每次运行 effect 的时,DOM 都已经更新完毕。
传递给 useEffect 的函数在每次渲染中都会有所不同,这是刻意为之的。每次我们重新渲染,都会生成新的 effect,替换掉之前的。某种意义上讲,effect 更像是渲染结果的一部分 —— 每个 effect “属于”一次特定的渲染。

useEffect(()=>{
  ... // 要做的事
  return () => {} // 清除操作
}, [依赖] )

为什么要在 effect 中返回一个函数?

这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。

React 何时清除 effect?

React 会在组件卸载的时候执行清除操作。正如之前学到的,effect 在每次渲染的时候都会执行。这就是为什么 React 会在执行当前 effect 之前对上一个 effect 进行清除。

通过跳过 Effect 进行性能优化

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

我们出入的第二个参数[count], 如果 count 的值是5, 下一次的值还是5, react会对这两次的值进行比较, 如果发现是相等的, 就会跳过这个 effect, 否则就会执行。
知识点: 如果你传入了一个空数组([]),effect 内部的 propsstate 就会一直拥有其初始值。尽管传入 [] 作为第二个参数更接近大家更熟悉的 componentDidMountcomponentWillUnmount 思维模式,但我们有更好的来避免过于频繁的重复调用 effect


自定义 Hook

通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。

// 提前公用逻辑到自定义Hook中
import React, { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}
// 使用自定义 Hook
function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

知识点: 自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook

在两个组件中使用相同的 Hook 会共享 state 吗?

不会。自定义 Hook 是一种重用状态逻辑的机制(例如设置为订阅并存储当前值),所以每次使用自定义 Hook 时,其中的所有 state 和副作用都是完全隔离的。


useContext

useContext(MyContext)等同于类中的静态contextType = MyContext,或者等同于<MyContext.Consumer>
useContext(MyContext)只允许您读取上下文并订阅其更改。您仍然需要树中的<MyContext.Provider>来提供此上下文的值


useReducer

useState的替代方案。接受类型为(state,action)=> newStatereducer,并返回与dispatch方法配对的当前状态。(参考 redux)

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

初始值也可以延迟初始化, useReducer(reducer, initialCount, init), init 是一个函数, 初始值将设置为init(initialArg)
如果从Reducer Hook返回与当前状态相同的值,则React将退出而不渲染子项或触发效果。 (React使用Object.is比较算法。)


useCallback

useCallback将返回一个回调的memoized(一种优化手段,遇到计算开销很大的函数时,会缓存其计算结果,下次同样的输入就可以直接返回缓存的结果)版本,该版本仅在其中一个依赖项发生更改时才会更改。

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

返回的就是一个memoized版本的 callback


useMemo

useMemo只会在其中一个依赖项发生更改时重新计算memoized值。此优化有助于避免在每个渲染上进行昂贵的计算

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

返回的就是一个memoized版本的值
useCallback(fn, deps) 相当于a useMemo(() => fn, deps).
请记住,传递给useMemo的函数在渲染期间运行。不要做那些在渲染时通常不会做的事情。例如,副作用应该用useEffect,而不是useMemo


useRef

useRef返回一个可变的ref对象,其.current属性被初始化为传递的参数(initialValue)。返回的对象将持续整个组件的生命周期

const refContainer = useRef(initialValue);

useRef()ref属性更有用。保持任何可变值的方法类似于在类中使用实例字段的方法。
useRef()创建了一个普通的JavaScript对象。 useRef()与自己创建{current:...}对象之间的唯一区别是useRef会在每个渲染上为您提供相同的ref对象。


useImperativeHandle

useImperativeHandle用于自定义暴露给父组件的ref属性。需要配合forwardRef一起使用

// 子组件
import React, { forwardRef, useImperativeHandle, useRef } from "react";

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} className="ipt" />;
}
export default FancyInput = forwardRef(FancyInput);
//父组件
import React, { Component, createRef } from "react";
import FancyInput from "./input";

export default class ImperativeApp extends Component {
  constructor(props) {
    super(props);
    this.inputRef = createRef();
  }

  render() {
    return (
      <>
        <FancyInput ref={this.inputRef} />
        <button
          className="btn"
          onClick={() => {
            this.inputRef.current.focus();
          }}
        >
          Click
        </button>
      </>
    );
  }
}


useLayoutEffect

签名和 useEffect 相同,但所有的 DOM突变后同步触发。使用它从 DOM 读取布局并同步重新渲染。在浏览器有机会绘制之前,将在 useLayoutEffect 内部计划的更新将同步刷新

在可能的情况下首选标准useEffect以避免阻止视觉更新
注意useLayoutEffect在与componentDidMountcomponentDidUpdate相同的阶段触发


useDebugValue

useDebugValue可用于在React DevTools中显示自定义挂钩的标签

上一篇 下一篇

猜你喜欢

热点阅读