前端修仙之路

React Hooks 入门

2019-11-24  本文已影响0人  一个笑点低的妹纸

目录


什么是 React Hooks?

Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.

Class?生命周期函数?this?
—— 可以通通say goodbye


Hooks 会使 React 变得臃肿吗?


为什么要创造 Hooks?

React 的使命:让开发者更容易地构建好的 UI。💪

为了这个目标,React 团队做了哪些努力呢?🌞

  1. 尝试简化复杂的东西(Suspense)
  2. 提升性能,尝试让 React 本身运行的更快(Time Slicing)
  3. 使用开发者工具帮助开发者 debug(Profile)

React 还存在什么糟糕的地方?

So,有没有什么方法来解决这三个问题呢?

Of course!React Hooks!🔥


内置的 Hook


Hook API 介绍

请参照 demo 示例 进行学习。

useState

传入值作为state的默认值,返回一个数组,数组的第一项是对应的状态(默认值会赋予状态),数组的第二项是更新状态的函数。

const [state, setState] = useState(initialState);
setState(newState);

不同于 setStateuseState 不会自动合并更新的对象,需要我们自己用对象扩展的形式合并,如果需要管理包含多个子值的状态对象,则用 useReducer 更为适合。

setState(prevState => {
  // Object.assign would also work
  return {...prevState, ...updatedValues};
});

Lazy initial state:如果初始状态是一个昂贵的计算的结果,你可以提供一个函数来进行计算,它只会在初始渲染时执行。

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

useEffect

可以在函数组件中执行副作用操作,并且是在函数渲染DOM完成后执行副作用操作,异步执行。

可以认为 useEffect 就是组合了 componentDidMountcomponentDidUpdate,以及 componentWillUnmount(在 useEffect 的回调中),但是又有区别,useEffect 不会阻止浏览器更新屏幕。

useEffect(() => {
  const subscription = props.source.subscribe();
  // 清理步骤,本质上就是消除副作用
  return () => {
    subscription.unsubscribe();
  };
}, [props.source]);

【注意】:永远要对 useEffect 的依赖诚实,被依赖的参数一定要填上去,否则会产生非常难以察觉与修复的 BUG。 (利用 eslint-plugin-react-hooks 自动订正你的代码中的依赖)

*useLayoutEffect

useEffect 相同,都是用来执行副作用,主要用来读取DOM布局并触发同步渲染,在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新,同步调用 effect。

官网建议还是尽可能的是使用标准的 useEffect 以避免阻塞视觉更新。


useCallback

可以认为是对依赖项的监听,接受一个回调函数和依赖项数组,返回一个该回调函数的 memoized(记忆)版本,该回调函数仅在某个依赖项改变时才会更新。

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

【用途】:解决将函数抽到 useEffect 外部的问题。

useMemo

主要用于渲染过程优化,两个参数依次是计算函数(通常是组件函数)和依赖状态列表,当依赖的状态发生改变时,才会触发计算函数的执行。

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

【好处】:相比于 React.memo,可以做到更细粒度的优化渲染。如函数 Child 整体可能用到了 A、B 两个 props,而渲染仅用到了 B,使用 React.memo() 方案时,A 的变化会导致重渲染,而使用 useMemo 的方案则不会。

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


useContext

useContext 接受一个 context 对象(由 createContext 创建)作为参数,并返回 Context.Consumer

  1. 创建一个 Context
const stateContext = createContext('default');
  1. 在根节点使用 Store.Provider 注入
function Parent() {
  const [count, setCount] = useState(0);
  return (
    <Store.Provider value={{ count }}>
      <Child />
    </Store.Provider>
  );
}
  1. 在子节点使用 useContext 拿到注入的数据
const Child = memo((props) => {
  const { count } = useContext(Store)
  // ...
});

【问题】:当函数多了,Provider 的 value 会变得很臃肿。
【解决】:使用 useReducer 解决这个问题。


useReducer

它和 Redux 的工作方式是一样的。useReducer 的出现是 useState 的替代方案,能够让我们更好的管理状态。

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

【arg1】:reducer。
【arg2】:指定状态的默认值。
【arg3】:接受一个函数作为参数,并把第二个参数当作函数的参数执行。主要作用是初始值的惰性求值,把一些对状态的逻辑抽离出来,有利于重置state。

// 定义一个init函数
function init(initialCount) {
    return [
        ...initialCount,
    ];
}

// useReducer使用
useReducer(reducer,[{id: Date.now(), value: "Hello react"}], init);

useRef

返回一个可变的 ref 对象,其 .current 属性初始化为传递的参数(initialValue)。返回的对象将持续整个组件的生命周期。事实上 useRef 是一个非常有用的 API,许多情况下,我们需要保存一些改变的东西,它会派上大用场的。

const refContainer = useRef(initialValue);

*useImperativeHandle

可以让你在使用 ref 时自定义暴露给父组件的实例值。
当我们使用父组件把 ref 传递给子组件的时候,这个Hook 允许在子组件中把自定义实例附加到父组件传过来的 ref 上,有利于父组件控制子组件。

useImperativeHandle(ref, createHandle, [deps])

*useDebugValue

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

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

  // Show a label in DevTools next to this Hook
  // e.g. "FriendStatus: Online"
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}

✌️ Hooks 的使用规则

Hooks 内部是如何工作的:

// 伪代码
let hooks, i;
function useState() {
  i++;
  if (hooks[i]) {
    // 再次渲染时
    return hooks[i];
  }
  // 第一次渲染
  hooks.push(...);
}

// 准备渲染
i = -1;
hooks = fiber.hooks || [];
// 调用组件
YourComponent();
// 缓存 Hooks 的状态
fiber.hooks = hooks;

第一条规则可以确保每次组件呈现时调用钩子的顺序是相同的。
【注意】自定义 Hooks 从技术上讲并不是 React 的特性。编写自定义 Hooks 的可行性源自于 Hooks 的设计方式。


封装 http hook

import { useState, useEffect } from 'react';

export const useHttp = (url, dependencies) => {
  const [isLoading, setIsLoading] = useState(false);
  const [fetchedData, setFetchedData] = useState(null);

  useEffect(() => {
    setIsLoading(true);
    console.log('Sending Http request to URL: ' + url);
    fetch(url)
      .then(response => {
        if (!response.ok) {
          throw new Error('Failed to fetch.');
        }
        return response.json();
      })
      .then(data => {
        setIsLoading(false);
        setFetchedData(data);
      })
      .catch(err => {
        console.log(err);
        setIsLoading(false);
      });
  }, dependencies);

  return [isLoading, fetchedData];
};

能在 useEffect 中使用 async/await 吗?

【结论】:不能在 useEffect 中返回一个 promise。JavaScript 异步函数总是返回一个promise,而 useEffect 应该只返回另一个函数,该函数用于清除效果。也就是说,如果你在useEffect 中启动 setInterval,你将返回一个函数(这里有一个闭包)来清除 interval。

➹ 参考



上一篇 下一篇

猜你喜欢

热点阅读