跟着 React 官方文档学 Hooks

栏目: 服务器 · 发布时间: 5年前

内容简介:前言:一直对这个新特性非常感兴趣,终于今天有时间,花了大半天时间,把优化一下:其中的

这篇文章两个月之前写的,看了一下官网文档没啥变化,就发出来了。如果有什么错误,欢迎指出~

前言:一直对这个新特性非常感兴趣,终于今天有时间,花了大半天时间,把 Hooks 的官方教程过了一遍,收获颇多,惊叹这个新特性真 TM 好用,以后开发用这个怕是要起飞了:laughing:。

状态钩子(State Hook)

const [state, setState] = useState(initialState);
复制代码
  1. 多个 useState 时, React 依赖于每次渲染时钩子的调用顺序都是一样的(存在与每个组件关联的“存储单元”的内部列表存放JavaScript对象),从而实现钩子与状态的一一对应关系。
  2. setState() 接收新的 state 或者一个返回 state 的函数( setCount(prevCount => prevCount - 1)} )。
  3. 不同于类组件中的 setStateuseState 返回的 setState 不会自动合并更新对象到旧的 state 中(可以使用 useReducer )。
  4. useState 可以接收一个函数返回 initialState ,它只会在初次渲染时被调用。
  5. setState 中的 state 和当前的 state 相等(通过 Object.is 判断),将会退出更新。
  6. 建议将一个状态根据哪些需要值一起变化拆分为多个状态变量。
const [rows, setRows] = useState(createRows(props.count));  // `createRows()`每次将会渲染将会被调用
复制代码

优化一下:

const [rows, setRows] = useState(() => createRows(props.count));  // `createRows()`只会被调用一次
复制代码

其中的 () => createRows(props.count) 会赋值给 rows ,这样就保证了只有在 rows 调用时,才会创建新的值。

作用钩子(Effect Hook)

useEffect(didUpdate);
复制代码
  1. 相当于生命周期函数 componentDidMount , componentDidUpdate , componentWillUnmount 的组合。
  2. 可以返回一个函数( cleanup )用于清理。
  3. 每次重新渲染都将会发生 cleanup phase :arrow_double_down:
useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
复制代码
componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  // ====== 原因在这里 ======
  componentDidUpdate(prevProps) {
    // Unsubscribe from the previous friend.id
    ChatAPI.unsubscribeFromFriendStatus(
      prevProps.friend.id,
      this.handleStatusChange
    );
    // Subscribe to the next friend.id
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
复制代码
// Mount with { friend: { id: 100 } } props
ChatAPI.subscribeToFriendStatus(100, handleStatusChange);     // Run first effect

// Update with { friend: { id: 200 } } props
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // Clean up previous effect
ChatAPI.subscribeToFriendStatus(200, handleStatusChange);     // Run next effect

// Update with { friend: { id: 300 } } props
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // Clean up previous effect
ChatAPI.subscribeToFriendStatus(300, handleStatusChange);     // Run next effect

// Unmount
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // Clean up last effect
复制代码
  1. useEffect(() => {document.title = You clicked ${count} times;}, [count]); ,指定第二个参数(这里为[ count ])变化时才发生 cleanup phase ,然后执行 effect
  2. 上面情况,如果 useEffect 第二个参数为为 [] 则表示只运行一次( componentDidMount 中执行 effectcomponentWillUnmount 中进行 cleanup ),永远不重新运行。
  3. componentDidMount / componentDidUpdate 有区别的地方在于, useEffect 中的函数会在 layoutpaint 结束后才被触发。(可以使用 useLayoutEffect 在下一次渲染之前(即 DOM 突变之后)同步触发)
  4. useEffect 虽然被推迟到浏览器绘制完成之后,但是肯定在有任何新的呈现之前启动。因为 React 总是在开始更新之前刷新之前渲染的效果。

其他钩子

useContext

const context = useContext(Context);
复制代码

接受一个上下文对象(由 React.createContext 创建),返回当前上下文值(由最近的上下文提供)。

附加钩子(Additional Hooks)

基本钩子的变体或用于特定边缘情况的钩子。

useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);
复制代码
  1. 第三个参数 init 为函数,将会这样调用: init(initialArg) ,返回初始值。
  2. 如果返回 state 和现在的 state 一样,将会在不影响子孙或者触发效果的情况下退出渲染。

useCallback

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

传入一个内联回调和一个输入数组,返回一个带有记忆的 函数 ,只有输入数组中其中一个值变化才会更改。 useCallback(fn, inputs) 等价于 useMemo(() => fn, inputs)

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
复制代码

传入一个创建函数和一个输入数组,返回一个带有记忆的 ,只有输入数组中其中一个值变化才会重新计算。

useRef

const refContainer = useRef(initialValue);
// ...
<input ref={refContainer} />
...
复制代码

返回一个可变的 ref 对象,可以自动将 ref 对象中的 current 属性作为初始值传递的参数,保持到组件的整个生命周期。

与在类中使用实例字段的方式类似,它 可以保留任何可变值

如保存前一个状态:

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

  const prevCountRef = useRef();
  useEffect(() => {
    prevCountRef.current = count;
  });
  const prevCount = prevCountRef.current;

  return <h1>Now: {count}, before: {prevCount}</h1>;
}
复制代码

useImperativeHandle

useImperativeHandle(ref, createHandle, [inputs])
复制代码

自定在使用 ref 时,公开给父组件的实例值,必须和 forwardRef 一起使用。

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

复制代码
<FancyInput ref={fancyInputRef} />

// 调用
fancyInputRef.current.focus()
复制代码

useLayoutEffect

使用方法和 useLayoutEffect 一致,不过它是在 DOM 读取布局时同步触发(相当于 componentDidMountcomponentDidUpdate 阶段)。(建议尽可能使用 useEffect 避免阻塞可视化更新)

useDebugValue

useDebugValue(value)
复制代码

用于在 React DevTools 中显示自定义钩子的标签,对于自定义钩子中用于共享的部分有更大价值。

自定义显示格式:

useDebugValue(date, date => date.toDateString());
复制代码

钩子(Hooks)规则

1. 只能在顶层调用,不能再循环、条件语句和嵌套函数中使用。 (原因:[State Hook](#State Hook) 第1条)

正确做法:

useEffect(function persistForm() {
      // :+1: We're not breaking the first rule anymore
      if (name !== '') {
        localStorage.setItem('formData', name);
      }
    });
复制代码

2. 只能在 React 函数组件中被调用。(可以通过自定义钩子函数解决)

可以使用 eslint-plugin-react-hooks 来强制自动执行这些规则。

自定义钩子(Hook)

  1. use 开头,一种公约。
  2. 自定钩子是一种复用状态逻辑的机制(例如设置订阅和记住当前值),每次使用,内部所有状态和作用都是独立的。
  3. 自定义钩子每个状态独立的能力源于 useStateuseEffect 是完全独立的。

测试钩子(Hook)

function Example() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
复制代码

使用 ReactTestUtils.act()

import React from 'react';
import ReactDOM from 'react-dom';
import { act } from 'react-dom/test-utils';
import Counter from './Counter';

let container;

beforeEach(() => {
  container = document.createElement('div');
  document.body.appendChild(container);
});

afterEach(() => {
  document.body.removeChild(container);
  container = null;
});

it('can render and update a counter', () => {
  // Test first render and effect
  act(() => {
    ReactDOM.render(<Counter />, container);
  });
  const button = container.querySelector('button');
  const label = container.querySelector('p');
  expect(label.textContent).toBe('You clicked 0 times');
  expect(document.title).toBe('You clicked 0 times');

  // Test second render and effect
  act(() => {
    button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
  });
  expect(label.textContent).toBe('You clicked 1 times');
  expect(document.title).toBe('You clicked 1 times');
});
复制代码

建议使用 react-testing-library


以上所述就是小编给大家介绍的《跟着 React 官方文档学 Hooks》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

C++编程风格

C++编程风格

卡吉尔 / 聂雪军 / 机械工业出版社发行室 / 2007-1 / 25.00元

本书描述C++语言中较深层次的程序设计思想和使用方法,包含大量软件工程概念和设计模式,重点介绍大规模编程相关的内容,例如增加代码的可读性、可维护性、可扩展性以及执行效率等的方法。本书的示例代码都是从实际程序中抽取出来的,融人了作者的实际开发经验。讲解如何正确地编写代码以及避开一些常见的误区和陷阱,并给出了许多实用的编程规则,可快速提升读者的C++编程功力。   本书描述平实,示例丰富,适合有......一起来看看 《C++编程风格》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

SHA 加密
SHA 加密

SHA 加密工具