react hooks
Posted webchang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react hooks相关的知识,希望对你有一定的参考价值。
文章目录
强烈推荐这个react-hooks的学习教程:https://github.com/puxiao/react-hook-tutorial
react hooks
react hooks是什么?
(1). Hook是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用 state 以及其他的 React 特性
三个常用的Hook
(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()
useState
(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue);
(3). useState()说明:
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组,第1个为内部当前状态值,第2个为更新状态值的函数
(4). setXxx()2种写法:
- setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值。如果多次连续调用,最后只会更新最近一次的值(合并更新一次状态)。
- setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值。如果多次连续调用,参数value是最新的值,会更新多次状态。
function Demo()
// useState函数参数,初始化指定的值会在内部作缓存
// 如果要使用多个状态,需要编写多次React.useState()
const [count, setCount] = React.useState(1);
const [name, setName] = React.useState("小明");
function add()
// setCount(count + 1);
// setCount的第二种写法
setCount(count => count + 1);
function changeName()
setName("晓明");
return (
<div>
<h2>求和为:count</h2>
<h2>姓名:name</h2>
<button onClick=add>+1</button>
<button onClick=changeName>改名字</button>
</div>
);
setXxx是采用 “异步直接赋值” 的形式,并不会像类组件中的setState()那样做“异步对比累加赋值”。
“异步”?这里的“异步”和类组件中setState中的异步是同一个意思,都是为了优化React渲染性能而故意为之。也就是无法在setXxx之后立即拿到最新的值。
“直接赋值”:
1、在Hook中,对于简单类型数据,比如number、string类型,可以直接通过setXxx(newValue)直接进行赋值。
2、但对于复杂类型数据,比如array、object类型,若想修改其中某一个属性值而不影响其他属性,则需要先复制出一份,修改某属性后再整体赋值。
通过 setXxx 设置新值,但是如果新值和当前值完全一样,那么会引发React重新渲染吗?通过React官方文档可以知道,当使用 setXxx 赋值时,Hook会使用Object.is()来对比当前值和新值,结果为true则不渲染,结果为false就会重新渲染。
useEffect
(1)Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)。
(2)React中的副作用操作
- 发ajax请求数据获取
- 设置订阅 / 启动定时器
- 手动更改真实DOM
(3)语法和说明:useEffect(effect,[deps])函数可以传入2个参数,第1个参数为我们定义的执行函数、第2个参数是依赖关系(可选参数)。若一个函数组件中定义了多个useEffect,那么他们实际执行顺序是按照在代码中定义的先后顺序来执行的。
useEffect(() =>
// 在此可以执行任何带副作用操作
return () => // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
(4)可以把 useEffect Hook 看做如下三个函数的组合
- componentDidMount()
- componentDidUpdate()
- componentWillUnmount()
(5)在 React 组件中有两种常见副作用操作:需要清除的和不需要清除的。
- 有时候,我们只想**在 React 更新 DOM 之后运行一些额外的代码。**比如发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作。因为我们在执行完这些操作之后,就可以忽略他们了。
- 一些副作用是需要清除的。例如订阅外部数据源。这种情况下,清除工作是非常重要的,可以防止引起内存泄露!
如果React.useEffect只传入了第一个函数参数,相当于是检测所有的状态。当页面第一次加载时会执行一次,每当有状态更新时也会执行该函数,也就是在每次渲染的时候都会执行。相当于是componentDidMount和componentDidUpdate。
function Example()
const [count, setCount] = useState(0);
useEffect(() =>
document.title = `You clicked $count times`;
);
有些副作用可能需要清除,这是可以返回一个函数,返回的函数相当于是componentWillUnmount
useEffect(() =>
function handleStatusChange(status)
setIsOnline(status.isOnline);
// 订阅
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () =>
// 取消订阅
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
;
);
可以使用多个Effect Hook,Hook 允许我们按照代码的用途分离他们, 而不是像生命周期函数那样。React 将按照 effect 声明的顺序依次调用组件中的_每一个_ effect。
function FriendStatusWithCounter(props)
const [count, setCount] = useState(0);
useEffect(() =>
document.title = `You clicked $count times`;
);
const [isOnline, setIsOnline] = useState(null);
useEffect(() =>
function handleStatusChange(status)
setIsOnline(status.isOnline);
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () =>
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
;
);
// ...
如果传入第二个参数是空数组,则第一个函数参数只会执行一次,谁也不监测。则第一个函数参数相当于是componentDidMount
如果传入的第二个参数不是空数组,数组中有对应的元素[count],则只检测该状态的改变,状态改变时会重新执行函数。
useEffect(() =>
document.title = `You clicked $count times`
, [count]); // 仅在 count 更改时更新
总结:useEffect的第二个参数可分为以下几种情况。
1、若缺省,则组件挂载、组件重新渲染、组件即将被卸载前(return的函数),每一次都会触发该useEffect;
2、若传值,则必须为数组,数组的内容是函数组件中通过useState自定义的变量或者是父组件传值过来的props中的变量。在组件挂载时会执行一次,之后只有数组内的变量发生变化时才会触发useEffect;
3、若传值,但是传的是空数组 [],则表示该useEffect里的内容仅会在“挂载完成后和组件即将被卸载前(return的函数)”执行一次;
常见问题
下方案例中,count的值从0变为1后就不再变化?为什么?
function Demo()
const [count, setCount] = React.useState(1);
// 使用生命周期钩子,这里相当于是componentDidMount和componentWillUnmount的结合
React.useEffect(() =>
let timer = setInterval(() =>
setCount(count + 1);
, 1000);
return function ()
clearInterval(timer);
;
, []);
// 卸载组件
function unMount()
ReactDom.unmountComponentAtNode(document.getElementById("root"));
return (
<div>
<h2>count</h2>
<button onClick=unMount>卸载组件</button>
</div>
);
关键点在于我们对useEffect传入来一个空数组,并且使用了setCount(count + 1);
如果传入空的依赖数组 [],意味着该 hook 只在组件挂载时运行一次,并非重新渲染时。但如此会有问题,在 setInterval 的回调中,count 的值不会发生变化。因为当 effect 执行时,我们会创建一个闭包,并将 count 的值被保存在该闭包当中,且初值为 0。每隔一秒,回调就会执行 setCount(0 + 1),因此,count 永远不会超过 1。
只需要改为setCount(count => count + 1)就可以解决这个问题,这种更新不再依赖于外部的count变量。
useRef
(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = React.useRef()
useRef可以“获取某些组件挂载完成或重新渲染完成后才拥有的某些对象”的引用,且保证该引用在组件整个生命周期内固定不变,都能准确找到我们要找的对象。
useRef关联对象的2种用法:
1、针对 JSX组件(原生标签),通过属性 ref=xxxRef 进行关联。
2、针对 useEffect中的变量,通过 xxxRef.current 进行关联。
//先定义一个xxRef引用变量,用于“勾住”某些组件挂载完成或重新渲染完成后才拥有的某些对象
const xxRef = useRef(null);
//针对 JSX组件,通过属性 ref=xxxRef 进行关联
<xxx ref=xxRef />
//针对 useEffect中的变量,通过 xxxRef.current 进行关联
useEffect(() =>
xxRef.current = xxxxxx;
,[]);
补充说明:
1、useRef是专门针对函数组件的,如果是类组件则使用React.createRef()。
2、React.createRef()也可以在函数组件中使用。
只不过React.createRef创建的引用不能保证每次重新渲染后引用固定不变。如果你只是使用React.createRef“勾住”JSX组件转换后对应的真实DOM对象是没问题的,但是如果想“勾住”在useEffect中创建的变量,那是做不到的。二者都想可以“勾住”,只能使用useRef。
在线案例:https://codesandbox.io/s/useref-40sxf5
使用案例1
若我们有一个组件,该组件只有一个输入框,我们希望当该组件挂载到网页后,自动获得输入焦点。
需求分析:需要使用useRef “勾住”这个输入框,当它被挂载到网页后,通过操作原生html的方法,将焦点赋予该输入框上。
import React,useEffect,useRef from 'react'
function Component()
//先定义一个inputRef引用变量,用于“勾住”挂载网页后的输入框
const inputRef = useRef(null);
useEffect(() =>
//inputRef.current就是挂载到网页后的那个输入框,一个真实DOM,因此可以调用html中的方法focus()
inputRef.current.focus();
,[]);
return <div>
/* 通过 ref 属性将 inputRef与该输入框进行“挂钩” */
<input type='text' ref=inputRef />
</div>
export default Component
注意:
1、在给组件设置 ref 属性时,只需传入 inputRef,千万不要传入 inputRef.current。
2、在“勾住”渲染后的真实DOM输入框后,能且只能调用原生html中该标签拥有的方法。
使用案例2
若我们有一个组件,该组件的功能需求如下:
1、组件中有一个变量count,当该组件挂载到网页后,count每秒自动 +1。
2、组件中有一个按钮,点击按钮可以停止count自动+1。
import React,useState,useEffect,useRef from 'react'
function Component()
const [count,setCount] = useState(0);
const timerRef = useRef(null);//先定义一个timerRef引用变量,用于“勾住”useEffect中通过setIntervale创建的计时器
useEffect(() =>
//将timerRef.current与setIntervale创建的计时器进行“挂钩”
timerRef.current = setInterval(() =>
setCount((prevData) => return prevData +1);
, 1000);
return () =>
//通过timerRef.current,清除掉计时器
clearInterval(timerRef.current);
,[]);
const clickHandler = () =>
//通过timerRef.current,清除掉计时器
clearInterval(timerRef.current);
;
return (
<div>
count
<button onClick=clickHandler >stop</button>
</div>
)
export default Component
在 TS中使用 useRef 创建计时器注意事项
timerRef.current = setInterval(() =>
setCount((prevData) => return prevData +1);
, 1000);
如果是在 TS 语法下,上面的代码会报错误:不能将类型“Timeout”分配给类型“number”。
造成这个错误提示的原因是:
- TypeScript 是运行在 Nodejs 环境下的,TS 编译之后的代码是运行在浏览器环境下的。
- Nodejs 和浏览器中的 window 他们各自实现了一套自己的 setInterval
- 原来代码 timerRef.current = setInterval( … ) 中 setInterval 会被 TS 认为是 Nodejs 定义的 setInterval,而 Nodejs 中 setInterval 返回的类型就是 NodeJS.Timeout。
- 所以,我们需要将上述代码修改为:timerRef.current = window.setInterval( … ),明确我们调用的是 window.setInterval,而不是 Nodejs 的 setInterval。
useContext
useContext是来解决什么问题?
useContext是<XxxContext.Consumer>的替代品,可以大量简化获取共享数据值的代码。
补充说明:
1、函数组件和类组件,对于<XxxContext.Provider>、<XxxContext.Consumer>使用方式没有任何差别。
2、你可以在函数组件中不使用useContext,继续使用<XxxContext.Consumer>,这都没问题。只不过使用useContext后,可以让获取共享数据相关代码简单一些。
使用案例
const value = useContext(MyContext);
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。
useContext(MyContext) 只是让你能够_读取_ context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件_提供_ context。
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>
);
示例中是把XxxContext直接定义在组件内部,没有使用import,但是这种方式没有办法在其它组件中使用XxxContext。如果将XxxContext定义在外部单独的模块中,各个组件都可以引用。
下方示例中,ContextCmp --> MiddleCmp --> ChildCmp,ContextCmp组件要传递数据给ChildCmp
// context.js
import React from "react";
export const PersonContext = React.createContext( name: "a", age: 10 );
// ContextCmp.jsx
import PersonContext from "./context";
import MiddleCmp from "./MiddleCmp";
export default function ContextCmp()
return (
<div>
<h1>ContextCmp</h1>
<PersonContext.Provider value= name: "b", age: 1 >
<MiddleCmp />
</PersonContext.Provider>
</div>
);
// MiddleCmp.jsx
import ChildCmp from "./ChildCmp";
export default function MiddleCmp()
return (
<div>
<h2>MiddleCmp</h2>
<ChildCmp />
</div>
);
// ChildCmp.jsx
import useContext from "react";
import PersonContext from "./context";
export default function ChildCmp()
const data = useContext(PersonContext);
return (
<div>
<h3>ChildCmp</h3>
<div>
data.name--data.age
</div>
</div>
);
useContext只是简化了获取共享数据value的代码,但是对于<XxxContext.Provider>的使用没有做任何改变
为什么不使用redux
在Hook出现以前,React主要负责视图层的渲染,并不负责组件数据状态管理,所以才有了第三方Redux模块,专门来负责React的数据管理。
但是自从有了Hook后,使用React Hook 进行函数组件开发,实现数据状态管理变得切实可行。只要根据实际项目需求,使用useContext以及useReducer,一定程度上是可以满足常见需求的。
useReducer
useReducer(reducer,initialValue)函数通常传入2个参数,第1个参数为我们定义的一个“由dispatch引发的数据修改处理函数”,第2个参数为自定义数据的默认值,useReducer函数会返回自定义变量的引用和该自定义变量对应的“dispatch”。
useReducer是useState的升级版,可以实现复杂逻辑修改,而不是像useState那样只是直接赋值修改。补充说明:
1、在React源码中,实际上useState就是由useReducer实现的,所以useReducer准确来说是useState的原始版。
2、无论哪一个Hook函数,本质上都是通过事件驱动来实现视图层更新的。
代码形式:
import React, useReducer from 'react'; //引入useReducer
//定义好“事件处理函数” reducer
function reducer(state, action)
switch (action)
case 'xx':
return xxxx;
case 'xx':
return xxxx;
default:
return xxxx;
function Component()
//声明一个变量xxx,以及对应修改xxx的dispatch
//将事件处理函数reducer和默认值initialValue作为参数传递给useReducer
const [xxx, dispatch] = useReducer(reducer, initialValue);
//若想获取xxx的值,直接使用xxx即可
//若想修改xxx的值,通过dispatch来修改
dispatch('xx');
//请注意,上述代码中的action只是最基础的字符串形式,事实上action可以是多属性的object,这样可以自定义更多属性和更多参数值
//例如 action 可以是 type:'xx',param:xxx
使用案例
import React, useReducer, useState from "react";
function reducer(state, action)
const type, payload = action;
switch (type)
case "add":
return state + payload;
case "sub":
return state - payload;
case "mul":
return state * payload;
default:
return state;
export default function Count()
const [num, setNum] = useState(0);
const [count, dispatch] = useReducer(reducer, 0);
return (
<div>
<h3>count</h3>
react hooks之useDebounce useThrottle