React Hooks 入门
目录
- 什么是 React Hooks?
- 为什么要创造 Hooks?
- Hooks API 一览
- Hooks 使用规则
什么是 React Hooks?
Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.
- Hooks 是在 React 16.8 版本的新特性,允许你以函数组件的形式去写每个组件
- 每一个 Hook 是由 React 提供的函数,它可以让你在函数组件中“钩”连到一些 React 特性。
Class?生命周期函数?this?
—— 可以通通say goodbye
Hooks 会使 React 变得臃肿吗?
- hook 不包含任何破坏性的更改,它提供了使用我们已知的 React 特性的能力,如 state 、context 和生命周期。
- 将 减少编写 React 应用时需要考虑的概念数量。Hooks 可以使得你始终使用函数,而不必在函数、类、高阶组件和 reader 属性之间不断切换。
- 随之而来,代码量也会减少。😊
为什么要创造 Hooks?
React 的使命:让开发者更容易地构建好的 UI。💪
为了这个目标,React 团队做了哪些努力呢?🌞
- 尝试简化复杂的东西(Suspense)
- 提升性能,尝试让 React 本身运行的更快(Time Slicing)
- 使用开发者工具帮助开发者 debug(Profile)
React 还存在什么糟糕的地方?
- 📎 在组件间复用状态逻辑很难——嵌套地狱
- mixin
- props
- 高阶组件
- 📦 庞大的组件,逻辑分散杂乱无章,变得难以理解
- 😓 令人困惑的 Classes
So,有没有什么方法来解决这三个问题呢?
Of course!React Hooks!🔥
内置的 Hook
Hook API 介绍
请参照 demo 示例 进行学习。
useState
传入值作为state的默认值,返回一个数组,数组的第一项是对应的状态(默认值会赋予状态),数组的第二项是更新状态的函数。
const [state, setState] = useState(initialState);
setState(newState);
不同于 setState
,useState
不会自动合并更新的对象,需要我们自己用对象扩展的形式合并,如果需要管理包含多个子值的状态对象,则用 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
就是组合了 componentDidMount
,componentDidUpdate
,以及 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
- 创建一个 Context
const stateContext = createContext('default');
- 在根节点使用
Store.Provider
注入
function Parent() {
const [count, setCount] = useState(0);
return (
<Store.Provider value={{ count }}>
<Child />
</Store.Provider>
);
}
- 在子节点使用
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 的使用规则
- 只能在函数最外层调用 Hook,不要在循环、条件判断或者子函数中调用。(🤔️ Why?)
- 只在React函数组件中调用 Hooks(除非是自定义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。
➹ 参考
- React的今天和明天(第一部分)
- React的今天和明天(第二部分)
- 深入浅出 React Hooks
- 理解 React Hooks
- useEffect 完整指南
- 精读《Function Component入门》
- 30分钟精通React Hooks
- React Hooks 详解 【近 1W 字】+ 项目实战
- React16:Hooks总览,拥抱函数式 (这大概是最全的React Hooks吧)
- 【原】Under the hood of React’s hooks system
- 【译】React hooks:它不是一种魔法,只是一个数组——使用图表揭秘提案规则
- React Hooks 原理
- 【原】Making Sense of React Hooks
- 【译】理解React Hooks