React Hooks
作者:互联网
Hooks
概念
Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。Hook是 React 16.8 (当前版本18,项目使用17)的新增特性,它可以让你在不编写 class 的情况下使用 state
以及其他的 React 特性。
优势:
- 函数组件不能使用
state
,遇到交互更改状态等复杂逻辑时不能更好地支持,hooks
让函数组件更靠近class组件,拥抱函数式编程。 - 解决副作⽤问题,
hooks
出现可以处理数据获取、订阅、定时执行任务、手动修改 ReactDOM这些⾏为副作用,进行副作用逻辑。比如useEffect
。 - 更好写出有状态的逻辑重用组件。
- 让复杂逻辑简单化,比如状态管理:
useReducer
、useContext
。 - 函数式组件比class组件简洁,开发的体验更好,效率更⾼,性能更好。
- 更容易发现无用的状态和函数。
基础Hook
useState
1、参数:useState()
方法里面唯一的参数就是初始 state
2、返回值:当前 state 以及更新 state 的函数[count,setCount]
3、使用方式:
(1)使用直接调用count
即可
(2)更新值使用setCount(3)
import React, { useState } from 'react';
function Example() {
// 声明一个叫 "count" 的 state 变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
useEffect
Effect Hook 可以让你在函数组件中执行副作用操作,所谓副作用就是:数据获取,设置订阅以及手动更改 React 组件中的 DOM等等操作。
通过这个钩子可以告诉 React 组件需要在渲染后执行某些操作。React 会保存传递的函数(effect
),并且在执行 DOM 更新之后调用它。
注意事项:
1、useEffect
会在每次渲染后都执行
2、调用useEffect
需要在组件内部(如下:Example
内)
3、无需清除effect的写法
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
4、需清除effect的写法(通过返回函数来解决)
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
// 这里的useEffect返回了一个函数cleanup
// 这个函数会在effect清除是执行
// 对于这种添加和删除订阅的业务就可以用这种方式
// 当我们关闭或刷新浏览器是都会触发cleanup清除订阅的业务,有效防止内存泄漏风险
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
5、跳过 Effect 进行性能优化(减少不必要的渲染)
通过传入第二个参数解决,只有在第二个参数发生变化时才会重新渲染。
如果要使用此优化方式,需确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量(就是这个变量需要在外部声明并在effect
中使用),否则你的代码会引用到先前渲染中的旧变量。
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
useContext
实现组件通信的一种手段。
接收一个 context
对象(React.createContext
的返回值)并返回该 context
的当前值。当前的 context
值由上层组件中距离当前组件最近的 <MyContext.Provider>
的 value
prop
决定。
当组件上层最近的 <MyContext.Provider>
更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext
provider
的 context
value
值。即使祖先使用 React.memo
或 shouldComponentUpdate
,也会在组件本身使用 useContext
时重新渲染。
React.createContext
是react
使用context实现跨组件通信的基础(创建)
使用方式:
1、创建
const MyContext = React.createContext(themes.light);
2、传值(长辈组件)
function App() {
return (
<MyContext.Provider value={themes.dark}>
<div>
<MyButton />
</div>
</MyContext.Provider>
);
}
3、接收(孩子组件)
function MyButton() {
// useContext 接收值
const params = useContext(MyContext);
return (
<div>
接收到的context:{params}
</div>
);
}
其他Hook
useReducer
-
useReducer
是useState
的代替方案,主要用来解决复杂结构的state
和state
处理逻辑比较复杂的情况 -
useReducer
是单个组件状态管理,组件通讯还需要props
-
redux
是全局状态管理,多组件共享数据 -
useReducer
是用于提高应用性能的,当更新逻辑比较复杂时,我们应该考虑使用useReducer
-
redux
和vuex
类型事实上
useReducer
其实和数组的reducer
函数更像, 都是对复杂和大量对象进行某种运算得到一个简单结果, 不同的是useReducer
产生的结果是一个state
而已.实例(登录):
useState
方式:可以看出这种方式会有很多的
useState
,难以维护。function LoginPage() { const [name, setName] = useState(''); // 用户名 const [pwd, setPwd] = useState(''); // 密码 const [isLoading, setIsLoading] = useState(false); // 是否展示loading,发送请求中 const [error, setError] = useState(''); // 错误信息 const [isLoggedIn, setIsLoggedIn] = useState(false); // 是否登录 const login = (event) => { event.preventDefault(); setError(''); setIsLoading(true); login({ name, pwd }) .then(() => { setIsLoggedIn(true); setIsLoading(false); }) .catch((error) => { // 登录失败: 显示错误信息、清空输入框用户名、密码、清除loading标识 setError(error.message); setName(''); setPwd(''); setIsLoading(false); }); } return ( // 返回页面JSX Element ) }
useReducer
改造后,登录信息都在initState
中清晰好维护:const initState = { name: '', pwd: '', isLoading: false, error: '', isLoggedIn: false, } function loginReducer(state, action) { switch(action.type) { case 'login': return { ...state, isLoading: true, error: '', } case 'success': return { ...state, isLoggedIn: true, isLoading: false, } case 'error': return { ...state, error: action.payload.error, name: '', pwd: '', isLoading: false, } default: return state; } } function LoginPage() { const [state, dispatch] = useReducer(loginReducer, initState); const { name, pwd, isLoading, error, isLoggedIn } = state; const login = (event) => { event.preventDefault(); dispatch({ type: 'login' }); login({ name, pwd }) .then(() => { dispatch({ type: 'success' }); }) .catch((error) => { dispatch({ type: 'error' payload: { error: error.message } }); }); } return ( // 返回页面JSX Element ) }
useMemo
useMemo
是用来减少没必要的重复渲染,从而提高执行效率
代码示例:在下面这个代码中,当num
发生变化的时候会重新触发渲染,导致list重复渲染
import React, { useState } from "react";
export default function App() {
const [len] = useState(10);
const [num, setNum] = useState(0);
const list = [];
for (let i = 0; i < len; i++) {
console.log(1234);
list.push(<li>{i}</li>);
}
return (
<div>
<ul>{list}</ul>
<input
type="text"
value={num}
onChange={(e) => {
setNum(e.target.value);
}}
/>
</div>
);
}
使用useMemo
优化后代码:在num
发生变化后,list组件不会重新渲染,触发渲染的条件来自于useMemo
的第二个参数,只有数组中的变量发生变化才会触发list
的重新渲染
import React, { useState, useMemo } from "react";
export default function App() {
const [len] = useState(10);
const [num, setNum] = useState(0);
const list = useMemo(() => {
const list = [];
for (let i = 0; i < len; i++) {
console.log(1234);
list.push(<li>{i}</li>);
}
return list;
}, [len]);
return (
<div>
<ul>{list}</ul>
<input
type="text"
value={num}
onChange={(e) => {
setNum(e.target.value);
}}
/>
</div>
);
}
useCallback
useCallback
的作用是用来避免子组件不必要的reRender
(避免不必要的重复渲染),不是用来解决组件中有过多内部函数导致的性能问题
使用实例:
import {useCallback, useState} from "react";
export const UseCallbackExample = () => {
const [count, setCount] = useState(0);
const logCount = useCallback(() => {
console.log(count);
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
click me
</button>
<button onClick={logCount}>log count</button>
</div>
);
}
useMemo与useCallback
useCallback
和useMemo
主要用来做数据缓存,它们的更新依赖于第二个参数是否发生了变化。useMemo
和useCallback
是在页面渲染前执行的hooks,因此这两个hooks中执行的内容应该是纯函数,不会影响页面元素的更新,页面也就不会陷入死循环。
不同:
-
useMemo
返回的是一个变量的值,useCallback
返回的是一个函数。对应到function
组件最后要return
的html代码部分,useMemo
就是作为一个值来使用的,而useCallback
则是被绑定到onClick
上,作为要执行的函数。这就是它俩的本质区别。(useCallback
针对函数,useMemo
针对值) -
useCallback(fn, deps)
等价于useMemo(() => fn, deps)
。
useRef
作用:
-
useRef
用来获取DOM元素对象当我们需要获取元素对象的时候, 首先引入
useRef
, 其次调用useRef()
方法接收它的返回值,我们需要获取那个DOM元素就在那个DOM元素上进行绑定,通过ref属性将useRef
的返回值绑定到元素身上,这样useRef
的返回值,通过useRef
返回一个对象,对象内部有个current
属性,这个属性就对应着我们需要的元素对象;import React, {useRef} from "react"; function Ref (){ const box = useRef() return ( <div> <div ref={box}>useRef</div> <button onClick={() => console.log(box)}>+1</button> </div> ) } export default Ref;
-
保存数据
为什么不用
let
一个变量来保存数据, 因为在使用定时器更新状态数据时, 数值的每次变化都会引起组件的更新,每次更新都重新let一个变量,所以在进行解绑操作的时候,你的let变量为null
,它并没有保存定时器,所以以上场景需要使用useRef
进行保存数据,useRef
不会因为组件的更新而丢失数据,虽然组件进行了更新,但是通过useRef
保存的数据是不会丢失的,这里通过useRef
中的current
来进行保存也是官方要求的写法,所以如果你想要保存的数据不会因为组件的更新而丢失,就可以使用useRef
来保存数据// 代码实例——保存数据 import React, {useRef, useEffect, useState} from "react"; function Ref (){ let timerId = useRef() const [count, setCount] = useState(0) useEffect(() => { timerId.current = setInterval(() => { setCount(count => count + 1) }, 1000) }, []) const stop = () => { console.log(timerId) clearInterval(timerId.current) } return ( <div> <div>{count}</div> <button onClick={stop}>停止</button> </div> ) } export default Ref;
-
用
useRef
获取组件的方法import React, { useRef } from 'react' import { Child } from './child' export default function App() { const onRef = useRef(); const pOnclick = ()=>{ onRef.current.onclickAction(); } return ( <div> <span onClick={pOnclick}>点我调用子组件方法</span> <Child onRef={onRef} /> </div> ) }
useImperativeHandle
正常情况下 ref
是不能挂在到函数组件上的,因为函数组件没有实例,但是 useImperativeHandle
为我们提供了一个类似实例的东西。在函数组件中就可以通过useImperativeHandle
将子组件的方法暴露出去供其他使用。
结合useRef
第三点可以实现父组件调用子组件方法
import React, { useImperativeHandle } from 'react'
export const Child = ({ onRef }) => {
const onclickAction = () => {
console.log("我是子组件 我被调用了!!!")
}
useImperativeHandle(onRef, () => ({
onclickAction,
}))
return (
<span onClick={onclickAction} />
)
}
useLayoutEffect
用法同useEffect
区别如下:
useEffect
是异步执行的,而useLayoutEffect
是同步执行的。useEffect
的执行时机是浏览器完成渲染之后,而useLayoutEffect
的执行时机是浏览器把内容真正渲染到界面之前,和componentDidMount
等价。
总结:
- 优先使用
useEffect
,因为它是异步执行的,不会阻塞渲染 - 会影响到渲染的操作尽量放到
useLayoutEffect
中去,避免出现闪烁问题 useLayoutEffect
和componentDidMount
是等价的,会同步调用,阻塞渲染useLayoutEffect
在服务端渲染的时候使用会有一个warning
,因为它可能导致首屏实际内容和服务端渲染出来的内容不一致(解决这个问题需要自定义hook判断环境决定使用useEffect
还是useLayoutEffect
)。
样例:
import * as React from 'react';
function Test() {
const [state, setState] = React.useState('hello world');
React.useEffect(() => {
console.log('useEffect');
setState('hello useEffect');
}, []);
React.useLayoutEffect(() => {
console.log('useLayoutEffect');
setState('hello useLayoutEffect');
}, []);
return <div>{state}</div>;
}
export default Test;
注意:渲染顺序如下:hello world-->hello useLayoutEffect-->hello useEffect
usedebugvalue
使用实例:
import { useState, useEffect, useDebugValue } from 'react'
const Hook = () => {
const [ isGone, setIsGone ] = useState(true)
// 我认为useDebugValue就是用来说明自定义hook标签是做什么的
useDebugValue('xm isGone', (key) => {
return `${key}_${new Date().getMinutes()}: ${new Date().getSeconds()}`
})
useEffect(() => {
setIsGone(false)
return () => {
setIsGone(true)
};
}, [])
return isGone
}
export default Hook
自定义Hook
通过自定义 Hook,可以将组件逻辑提取到可重用的函数中,本质上只是一种函数代码逻辑的抽取。
自定义 Hook 是一个函数,其名称必须以use
开头,函数内部可以调用其他的 Hook。
1、使用公共的userContext
export default function useUserContext() {
const user = useContext(UserContext);
const token = useContext(TokenContext);
return [user, token];
}
import React, { useContext } from 'react';
import useUserContext from '../hooks/user-hook';
export default function CustomContextShareHook() {
const [user, token] = useUserContext();
console.log(user, token);
return (
<div>
<h2>CustomContextShareHook</h2>
</div>
)
}
2、使用公共的数据存储的localStorage
// hooks/local-store-hook.ts
import React,{useState, useEffect} from 'react';
function useLocalStorage(key) {
const [data, setData] = useState(() => {
return JSON.parse(window.localStorage.getItem(key))
});
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(data));
}, [data]);
return [data, setData];
}
export default useLocalStorage;
// Customer.tsx
import React, { useState, useEffect } from 'react';
import useLocalStorage from '@/hooks/local-store-hook';
export default function CustomDataStoreHook() {
const [name, setName] = useLocalStorage("name");
return (
<div>
<h2>CustomDataStoreHook: {name}</h2>
<button onClick={e => setName("kobe")}>设置name</button>
</div>
)
}
Hook规则
只在最顶层使用 Hook
不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState
和 useEffect
调用之间保持 hook 状态的正确。
function Form() {
const [name, setName] = useState('Mary');
// 在条件语句中使用 Hook 违反第一条规则
if (name !== '') {
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
}
const [surname, setSurname] = useState('Poppins');
useEffect(function updateTitle() {
document.title = name + ' ' + surname;
});
}
///////////////////////////////////////////////////////////////////////////////////////////////
// 会出现当下的结果
useState('Mary') // 1. 读取变量名为 name 的 state(参数被忽略)
// useEffect(persistForm) // 标签:const,Hooks,React,useState,组件,return,useEffect
来源: https://www.cnblogs.com/yangguanglei/p/16598355.html