React Hook 01 - 官网简翻

1,260 阅读4分钟

此文仅为官网部分材料整理供个人使用,大家直接看 React Hook 官网 介绍,不要看这里。

为什么要搞 Hooks

  1. 在组件之间复用状态逻辑很难
  2. 复杂组件变得难以理解(在生命周期函数里做各种事情)
  3. 难以理解的 class

使用 useState

如何使用

[状态变量, 更改函数] = useState(初始值)

如何记录值

  1. 根据 useState 出现的顺序来确定的 值的 “位置”
  2. 即每一次 [状态变量, 更改函数] = useState(初始值) 调用,都在闭包内创建一个空间,以顺序做 index 提取
  3. 基于第二点,useState 必须写在函数最外层,避免嵌套在 if / else 逻辑内

使用 useEffect

注意

useEffect 运行时机类同于 componentDidMount、componentDidUpdate、componentWillUnmount 这三个生命周期函数,执行 Dom 更新后,才会调用

如何使用

useEffect(() => {
  // do something
  document.title = `You clicked ${count} times`;
})

如何清除

useEffect(() => {
  // do something
  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);

  // 通过返回一个回调函数的方式来注销 
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  }
})

跳过一些不必要的副作用函数

useEffect(() => {
  document.title = `You clicked ${count} times`;
  // 只有当count的值发生变化时,才会重新执行`document.title`这一句
  // 不要传递空数组,可能会有其他问题
}, [count]);

规则

  1. 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用
  2. 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中,我们稍后会学习到。)

eslint-plugin-react-hooks

每次 State Hook 函数调用,都是全新的一个 state。 每次 Effect Hook 调用,都是重新的一个新函数执行,每个 effect “属于”一次特定的渲染。

自定义 Effect Hooks

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 API 索引

基础 Hook

useState

useEffect

useContext

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  // useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer>
  const theme = useContext(ThemeContext);

  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

额外的 Hook

useReducer

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

useCallback

当把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用

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

// a, b 改变时,才会执行 doSomething
const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

useMemo

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

你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。将来,React 可能会选择“遗忘”以前的一些 memoized 值,并在下次渲染时重新计算它们,比如为离屏组件释放内存。先编写在没有 useMemo 的情况下也可以执行的代码 —— 之后再在你的代码中添加 useMemo,以达到优化性能的目的

简言: 先不要用,以后有需要再用

useRef

const refContainer = useRef(initialValue);

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

useImperativeHandle

可以让你在使用 ref 时自定义暴露给父组件的实例值,个人感觉:这个基本上应该用不太到

useImperativeHandle(ref, createHandle, [deps])

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

useLayoutEffect

其函数签名与 useEffect 相同,但它会在 所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

尽可能使用标准的 useEffect 以避免阻塞视觉更新

推荐你一开始先用 useEffect,只有当它出问题的时候再尝试使用 useLayoutEffect

useDebugValue

用于在 React 开发者工具中显示 自定义 hook 的标签

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

  // ...

  // 在开发者工具中的这个 Hook 旁边显示标签
  // e.g. "FriendStatus: Online"
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}

FAQ

我该如何使用 Hook 进行数据获取?

请求的 fetch 放在 useEffect 的回调函数内

如何获取上一轮的 props 或 state?

function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);
  return <h1>Now: {count}, before: {prevCount}</h1>;
}

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

该如何测量 DOM 节点?

你可以使用 callback ref

function MeasureExample() {
  const [height, setHeight] = useState(0);

  const measuredRef = useCallback(node => {
    if (node !== null) {
      setHeight(node.getBoundingClientRect().height);
    }
  }, []);

  return (
    <>
      <h1 ref={measuredRef}>Hello, world</h1>
      <h2>The above header is {Math.round(height)}px tall</h2>
    </>
  );
}

在这个案例中,我们没有选择使用 useRef,因为当 ref 是一个对象时它并不会把当前 ref 的值的 变化 通知到我们。使用 callback ref 可以确保 即便子组件延迟显示被测量的节点 (比如为了响应一次点击),我们依然能够在父组件接收到相关的信息,以便更新测量结果。

注意到我们传递了 [] 作为 useCallback 的依赖列表。这确保了 ref callback 不会在再次渲染时改变,因此 React 不会在非必要的时候调用它。

抽象封装

function useClientRect() {
  const [rect, setRect] = useState(null);
  const ref = useCallback(node => {
    if (node !== null) {
      setRect(node.getBoundingClientRect());
    }
  }, []);
  return [rect, ref];
}