前言
去年在工作之余花了几天时间自学了React
并独自开发了工作中的一个项目,但也仅仅为浅层技术。所以在上个月跳槽时当多个面试官问到React-Hooks
时会觉得有点生疏,后来专门去了解学习才理解了其作用及使用。下面来分享一些自己学习的资料整理,希望可以帮助想要接触react
的你。
有接触过react
的朋友都知道react
分为函数组件和类组件,我们先来看看这两个组件的区别。
我们知道在函数组件中没办法使用状态,只能作为展示组件(就是个花瓶...哎)。
但是如果我想让函数组件也有自己的状态怎么办?难道函数组件就不配拥有状态吗?每次写个方法还得手动改变this指向?oh my god ! 有了
Hooks
,这些都再也不是问题了~
首先 让我们谈谈什么是react hook
。先剖出官方解释:
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
Hook
顾名思义就是钩子的意思。在函数组件中把React
的状态和生命周期等这些特性钩入进入,这就是React
的Hook
,那么 React Hooks
相比于类组件到底有哪些好处呢?
-
代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过
React Hooks
可以将功能代码聚合,方便阅读维护 -
组件树层级变浅,在原本的代码中,我们经常使用
HOC/render props
等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在React Hooks
中,这些功能都可以通过强大的自定义的Hooks
来实现
Hook规则
Hook
可以让你在不编写 class
组件的情况下使用 state
以及其他的 React
特性。但是,有些规则是我们需要遵守的:
- 只能在函数内部的最外层调用 Hook
不要在循环,条件或嵌套函数中调用 Hook
确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。- 只能在 React 的函数组件(非 class组件)中调用 Hook
不要在普通的 JavaScript 函数或 class 组件中调用 Hook。你可以:
(1)在 React 的函数组件中调用 Hook;
(2)在自定义 Hook 中调用其他 Hook。
下面,我们开始认识常用的 Hook API—— useState
、useEffect
、useContext
、useReducer
。
useState 保存组件状态
在类组件中,我们使用 this.state
来保存组件状态,并对其修改触发组件重新渲染。比如下面这个简单的计数器组件,很好诠释了类组件如何运行:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
现在我们用hook来实现同等方法:
import React, { useState } from 'react';
function Example() {
// 声明一个叫 "count" 的 state 变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
这样比较下来,很简单的一个加1计数器,Hook 写法比 class 组件是不是简洁了很多。
下面,我们来分析如何使用 useState Hook
...
// 第一步:从 react 库中引入 useState Hook
import React, { useState } from 'react';
function Example() {
/* 第二步:通过调用 useState Hook 声明了一个新的 state 变量。
* 它返回一对值(数组)解构到我们命名的变量上。
* 第一个返回的是状态 count,它存储的是点击次数。我们通过传 0 作为 useState 唯一的参数来将其初始化 0。
* 第二个返回的值本身就是一个函数。它让我们可以更新 count 的值,所以我们叫它 setCount。
*/
const [count, setCount] = useState(0); // 声明一个叫 "count" 的 state 变量
return (
<div>
<!-- 第三步:读取 state,即count -->
<p>You clicked {count} times</p>
<!-- 第四步:更新 state,通过 setCount() -->
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
通过上面的分析,我们可以看到使用 useState Hook
管理状态简直太爽了。不用写繁琐的 class
组件,不用担心 this
指向,代码是如此的清晰。
现在就会有朋友问了,那如何如何使用多个 state
变量呢,如下:
将 state
变量声明为一对[something, setSomething]
也很方便,因为如果我们想使用多个 state
变量,它允许我们给不同的 state
变量取不同的名称:
function ExampleWithManyStates() {
// 声明多个 state 变量
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: '学习 Hook' }]);
...
在以上组件中,我们有局部变量 age
,fruit
和 todos
,并且我们可以单独更新它们:
function handleOrangeClick() {
// 和 this.setState({ fruit: 'orange' }) 类似
setFruit('orange');
}
你不必使用多个 state
变量。State
变量可以很好地存储对象和数组,因此,你仍然可以将相关数据分为一组。然而,不像 class
中的 this.setState
,更新 state
变量总是替换它而不是合并它。
useEffect 处理副作用
函数组件能保存状态,但是却无法执行异步请求等副作用的操作,所以 React 提供了 useEffect
来帮助开发者处理函数组件的副作用操作。
Effect Hook定义:useEffect
传入一个 callback 函数。
useEffect(effect: React.EffectCallback, deps?: ReadonlyArray<any> | undefined)
Effect Hook
作用:
处理函数组件中的副作用,如异步操作、延迟操作等,可以替代Class Component
的componentDidMount
、componentDidUpdate
、componentWillUnmount
等生命周期。
Effect Hook
特性:
- effect(副作用):指那些没有发生在数据向视图转换过程中的逻辑,如 ajax 请求、访问原生dom 元素、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器、记录日志等。
- 副作用操作可以分两类:需要清除的和不需要清除的。
-
useEffect
就是一个Effect Hook
,给函数组件增加了操作副作用的能力。它和class
组件中的componentDidMount
、componentDidUpdate
和componentWillUnmount
具有相同的用途,只不过被合并成了一个 API。 -
useEffect
接收一个函数,该函数会在组件渲染到屏幕之后才执行。该函数有要求:要么返回一个能清除副作用的函数,要么就不返回任何内容 - 与
componentDidMount
或componentDidUpdate
不同,使用useEffect
调度的effect
不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect
不需要同步地执行。在个别情况下(例如测量布局),有单独的useLayoutEffect Hook
供你使用,其 API 与useEffect
相同
useEffect
使用示例:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 类似 componentDidMount 和 componentDidUpdate
useEffect(() => {
// 使用浏览器 API 去更新 document 标题
document.title = `You clicked ${count} times`;
});
// 类似 componentDidMount
useEffect(() => {
// 使用浏览器 API 去更新 document 标题
document.title = `You clicked ${count} times`;
}, []); // 慎用!监听空数组,当 callback 使用到 state 或 props 时最好不要用,因为只能获取初始化的数据
// 返回一个函数用于清除操作
useEffect(() => {
document.title = `You clicked ${count} times`;
window.addEventListener('load', loadHandle); // loadHandle 函数定义省略
return () => {
window.removeEventListener('load', loadHandle); // 执行清理:callback 下一次执行前调用
};
}, [count]); // 只有当count的值发生变化时,才会重新执行 callback
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useEffect
用法很简单,但是有两个地方需要特别注意:
- deps 参数很重要
(1)useEffect
可以接受第二个参数deps
,用于在re-render
时判断是否重新执行callback
(2)deps
数组项必须是immutable
的,比如:不能也不必传useRef
、dispatch
等
(3)deps
的比较是浅比较,传入对象、函数是无意义
(4) 作为最佳实践,使用useEffect
时请尽可能都传deps
- 清除副作用
(1)useEffect
传入的callback
要么返回一个清除副作用的函数,要么什么都不返回。所以,callback
不能用async 函数
(面试题:如何在useEffect
中使用async
函数)
(2)useEffect
传入的callback
返回一个函数,在下一次执行callback
前将会执行这个函数,从而达到清理effect
的效果
useEffect
的用法大概就是这样的,有一些坑和更复杂操作这里没有涉及。当然,要深入理解的话需要去啃源码了,这里不做过多的解释。
useContext 祖孙传值
useContext
是 React
帮你封装好的,用来处理多层级传递数据的方式,在以前组件树种,跨层级祖先组件想要给孙子组件传递数据的时候,除了一层层 props
往下透传之外,还可以使用useContext
来解决爷孙组件的传值问题,新的Context API
使用订阅发布者模式方式实现在爷孙组件中传值
const { Provider, Consumer } = React.createContext(null);
function Bar() {
return <Consumer>{value => <div>{value}</div>}</Consumer>;
}
function Foo() {
return <Bar />;
}
function App() {
return (
<Provider value={"hello context"}>
<Foo />
</Provider>
);
}
通过 React createContext
的语法,在 APP
组件中可以跨过 Foo
组件给 Bar
传递数据。而在 React Hooks
中,我们可以使用 useContext
进行改造。
const Context = React.createContext("hello");
function Bar() {
const val = useContext(Context); // 使用useContext直接取值
return <div>{val}</div>;
}
function Foo() {
return <Bar />;
}
function App() {
return (
<Context.Provider value={"hello react"}>
<Foo />
</Context.Provider>
);
}
传递给 useContext
的是 context
而不是 consumer
,返回值即是想要透传的数据了。用法很简单,使用 useContext
可以解决 Consumer
多状态嵌套的问题。
而使用 useContext 则变得十分简洁,可读性更强且不会增加组件树深度。
useReducer
看到useReducer
,肯定会想到Redux
,没错它和Redux
的工作方式是一样的。useReducer
的出现是useState
的替代方案,能够让我们更好的管理状态,但是缺点也是存在的,就是无法实现redux
的中间件
import React, { useReducer } from "react";
const initialState = {
count: 0
};
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + action.payload };
case "decrement":
return { count: state.count - action.payload };
default:
throw new Error();
}
}
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "increment", payload: 5 })}>
+
</button>
<button onClick={() => dispatch({ type: "decrement", payload: 5 })}>
-
</button>
</>
);
}
用法跟 Redux
基本上是一致的,如果你学过redux
,对于useReducer
基本没有学习门槛
总结
Hook
让我们可以在函数组件中使用状态state,函数组件一统 React 的时代来了,这很棒。
Hook
可以让我们摒弃那些繁琐的生命周期、不用考虑 this
的指向、复用逻辑也不用写HOC
(高阶组件)了,这很棒。
Hook
还有更多 API 等着我们去探索,同时也支持自定义 Hook。
(以上为网络中资料的整理,若侵权删)