react hook
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
为什么需要hook?
- 在组件之间复用状态逻辑很难,providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”。而Hook 使你在无需修改组件结构的情况下复用状态逻辑。
- 每个生命周期常常包含一些不相关的逻辑,相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。在多数情况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)
- class 是学习 React 的一大屏障。你必须去理解 JavaScript 中
this
的工作方式,这些代码非常冗余,Hook 使你在非 class 的情况下可以使用更多的 React 特性
ps:react团队将继续为 class 组件提供支持 Hook 是向下兼容的
在 Facebook,我们有成千上万的组件用 class 书写,我们完全没有重写它们的计划。相反,我们开始在新的代码中同时使用 Hook 和 class。
hook是什么?
- Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数
- Hook 就是 JavaScript 函数
hook使用规则
- 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
- 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用
- 自定义的 Hook 中也可以调用hook
hook | 简介 | |
---|---|---|
1 | useState | 声明一个新的 state 变量,通过在函数组件里调用它来给组件添加一些内部 state |
2 | useEffect | 给函数组件增加了操作副作用的能力 (数据获取、订阅或者手动修改过 DOM称为“副作用”,或者简称为“作用”) |
3 | 自定义hook | 组件之间重用一些状态逻辑,自定义 Hook 可以让你在不增加组件的情况下达到同样的目的 |
4 | useContext | 帮助我们跨越组件层级直接传递变量,useContext 接收上下文变量,实现共享 |
5 | useCallback | 在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行,返回缓存的函数 |
6 | useMemo | 在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行,返回缓存的变量 |
7 | useReducer | reducer是一个函数(state, action) => newState :接收当前应用的state和触发的动作action,计算并返回最新的state。 |
8 | useRef | useRef 返回一个可变的 ref 对象 |
9 | useImperativeHandle | useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值 |
10 | useLayoutEffect | 大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同 |
一、useState
简介:
- 函数组件里调用useState来给组件添加一些内部 state
- React 会在重复渲染时保留这个 state
useState
会返回一对值:当前状态和一个让你更新它的函数- 你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的
this.setState
- 它不会把新的 state 和旧的 state 进行合并 ,更新 state 变量是替换
- 你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的
useState
只有一个参数,唯一的参数就是初始 state 相当于设置初始值- 可以在一个组件中多次使用 State Hook ,声名多个state变量
PS:
1. **useState 返回一个有两个元素的数组** (重点: 数组 第一元素-当前state 第二元素-更新该state的函数)
2. **[数组解构](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Array_destructuring)的语法让我们在调用 `useState` 时可以给 state 变量取不同的名字**
对比 :
读取state
在 class 中 | 在函数中 |
---|---|
this.state.count 读取 | 直接用 count |
<p>You clicked {this.state.count} times</p> | <p>You clicked {count} times</p> |
更新 State
在class中 | 在函数中 |
---|---|
调用 this.setState() 更新 count 值 | setCount() 更新count值 |
<button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> | <button onClick={() => setCount(count + 1)}> Click me </button> |
运用:
1: import React, { useState } from 'react';
2:
3: function Example() {
4: const [count, setCount] = useState(0);
5:
6: return (
7: <div>
8: <p>You clicked {count} times</p>
9: <button onClick={() => setCount(count + 1)}>
10: Click me
11: </button>
12: </div>
13: );
14: }
- 第一行: 引入 React 中的
useState
Hook。它让我们在函数组件中存储内部 state。 - 第四行: 在
Example
组件内部,我们通过调用useState
Hook 声明了一个新的 state 变量。它返回一对值给到我们命名的变量上。我们把变量命名为count
,因为它存储的是点击次数。我们通过传0
作为useState
唯一的参数来将其初始化为0
。第二个返回的值本身就是一个函数。它让我们可以更新count
的值,所以我们叫它setCount
。 - 第九行: 当用户点击按钮后,我们传递一个新的值给
setCount
。React 会重新渲染Example
组件,并把最新的count
传给它。
二、useEffect
简介:
- 给函数组件增加了操作副作用的能力
- 它跟 class 组件中的
componentDidMount
、componentDidUpdate
和componentWillUnmount
具有相同的用途,只不过被合并成了一个 API - 副作用函数是在组件内声明的,它们可以访问到组件的 props 和 state
- React 会在每次渲染后调用副作用函数 包括第一次渲染的时候
- 副作用函数还可以通过返回一个函数来指定如何“清除”副作用 ?
- 可以在一个组件中多次使用 Effect Hook
- 默认情况下,
useEffect
会在每次渲染后都执行 useEffect
可以在组件渲染后实现各种不同的副作用。有些副作用可能需要清除,所以需要返回一个函数- **Hook 允许我们按照代码的用途分离他们,**React 将按照 effect 声明的顺序依次调用组件中的每一个 effect
PS:
- 如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为
useEffect
的第二个可选参数即可 - React 会跳过这个 effect,这就实现了性能的优化。
- 如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组(
[]
)作为第二个参数
(这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行) - React 会等待浏览器完成画面渲染之后才会延迟调用
useEffect
,因此会使得额外操作很方便
对比:
在class中 | 在函数中 |
---|---|
1.很多情况下,我们希望在组件加载和更新时执行同样的操作,从概念上说,我们希望它在每次渲染之后执行 2.React 的 class 组件没有提供这样的方法 3.即使我们提取出一个方法,我们还是要在两个地方调用它 | React 会保存你传递的函数(我们将它称之为 “effect”),并且在执行 DOM 更新之后调用它 “渲染之后”发生,不用再去考虑“挂载”还是“更新‘’ |
运用:
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
}
- 声明了
count
state 变量,并告诉 React 我们需要使用 effect - 在 effect 中获取到最新的
count
值,因为他在函数的作用域内 - 当 React 渲染组件时,会保存已使用的 effect,并在更新完 DOM 后执行它
- 这个过程在每次渲染时都会发生,包括首次渲染。
三、自定义hook
简介:
- 逻辑抽取到自定义 Hook里面,可以在各个组件中使用这个自定义hook
- 各个组件的 state 是完全独立的,不复用 state 本身,复用状态逻辑
- Hook 的每次调用都有一个完全独立的 state ---- 因此你可以在单个组件中多次调用同一个自定义 Hook。
- 函数 函数名以 “
use
” 开头并调用其他 Hook ==> 自定义 Hook
四、useContext
简介:
const value = useContext(MyContext);
接收一个 context 对象(React.createContext
的返回值)
并返回该 context 的当前值- 当前context 值由上层组件中距离当前组件最近的
<MyContext.Provider>
的value
prop 决定 - 需要在上层组件树中使用
<MyContext.Provider>
来为下层组件提供 context - 组件上层最近
<MyContext.Provider>
更新,Hook 会触发重渲染,使用最新contextvalue
值。
运用:
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() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
五、useCallback
简介:
- 在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行,返回缓存的函数
六、useMemo
简介:
- 在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行,返回缓存的变量
七、useReducer
简介:
- reducer是一个函数
(state, action) => newState
:接收当前应用的state和触发的动作action,计算并返回最新的state。
八、useRef
简介:
- 代码中用
useRef
创建了xxxx
对象,并将其赋给了button
的ref
属性。这样,通过访问xxxx.current
就可以访问到button
对应的DOM对象
运用:
import React, { useRef, useImperativeHandle } from 'react';
import ReactDOM from 'react-dom';
const FancyInput = React.forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} type="text" />
});
const App = props => {
const fancyInputRef = useRef();
return (
<div>
<FancyInput ref={fancyInputRef} />
<button
onClick={() => fancyInputRef.current.focus()}
>父组件调用子组件的 focus</button>
</div>
)
}
ReactDOM.render(<App />, root);
九、useImperativeHandle
简介:
useImperativeHandle
可以让你在使用ref
时自定义暴露给父组件的实例值。- 在大多数情况下,应当避免使用 ref 这样的命令式代码。
useImperativeHandle
应当与forwardRef
一起使用
语法:
useImperativeHandle(ref, createHandle, [deps])
运用:
import React, { forwardRef, useImperativeHandle, useEffect, useRef } from 'react'
const TestRef = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
open() {
//最后结果打印出了open。
console.log("open")
}
}))
})
function App () {
const ref = useRef()
useEffect(() => {
ref.current.open()
},[])
return(
<>
<div>kjs</div>
<TestRef ref={ref}></TestRef>
</>
)
}
export default App
十、useLayoutEffect
简介:
- 大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的
useLayoutEffect
Hook 供你使用,其 API 与useEffect
相同
tip:
-
Hook 需要在我们组件的最顶层调用
-
如果我们想要有条件地执行一个 effect,可以将判断放到 Hook 的内部:
// 🔴 在条件语句中使用 Hook 违反第一条规则 if (name !== '') { useEffect(function persistForm() { localStorage.setItem('formData', name); }); }
===========》》
useEffect(function persistForm() { // 👍 将条件判断放置在 effect 中 if (name !== '') { localStorage.setItem('formData', name); } });