跟着 React 官方文档学 Hooks

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

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

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

前言:一直对这个新特性非常感兴趣,终于今天有时间,花了大半天时间,把 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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

Dive Into Python 3

Dive Into Python 3

Mark Pilgrim / Apress / 2009-11-6 / USD 44.99

Mark Pilgrim's Dive Into Python 3 is a hands-on guide to Python 3 (the latest version of the Python language) and its differences from Python 2. As in the original book, Dive Into Python, each chapter......一起来看看 《Dive Into Python 3》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具