hooks + redux
一、hooks
1.什么是hooks
hooks是React V16.8中 新增的功能,它不仅解决了函数组件this指向混乱的问题,还比之前更好地进行逻辑的复用了
它为函数组件添加了状态、生命周期。所以从16.8版本后,react逐渐从
class组件(提供状态)+函数组件(展示内容)
变成现在的
hooks(提供状态)+函数组件(展示内容)
有了hooks后,函数组件就不能称为无状态组件了,因为hooks为函数组件提供了状态
2.useState
// 函数式组件声明状态和设置状态
// 第一步,从react中导入useState
import React, { useState } from 'react'
const App = () => {
// 第二步,useState()返回值是一个数组,第一个元素是状态,第二个元素是设置状态的方法
// 声明状态就给useState()里传参就行了
const [num, setNum] = useState(10)
const [arr, setArr] = useState([1, 2, 3, 4])
return (
<>
<h1>{num}</h1>
<h1>{arr}</h1>
{/* 第三步,设置状态直接在()里放新值,就不用采用之前的setState({})对象写法 */}
<button onClick={() => setNum(num + 10)}>点击+10</button><br />
<button onClick={() => setArr([...arr, 5])}>点击新增数字</button>
</>
)
}
export default App
tips:useState使用限制(react在底层为useState创建了一个链表,它记住了每个useState的顺序。当useState放在if语句或者是for语句里时,会造成顺序的混乱)
import React, { useState } from 'react'
const App = () => {
// useState()三个使用限制
const [arr, setArr] = useState([1, 2, 3, 4])
// 1.不能写在for语句里
// for(let i = 0;i<list.length;i++) {
// useState()
// }
// 2.不能写在if/while语句中
// if(flag) useState()
// 3.useState()只能写在非普通函数中
// 3.1 非普通函数:①函数式组件(函数名以大写字母开头,返回一段jsx或者null)②自定义hooks(以use开头)
return (
<>
<h1>{arr}</h1>
<button onClick={() => setArr([...arr, 5])}>点击新增数字</button><br />
</>
)
}
export default App
3.useEffect
import React, { useEffect, useState } from 'react'
const App = () => {
const [num, setNum] = useState(0)
// useEffect本质是一个钩子函数(用于处理副作用,也就是与页面渲染无关的操作,例如 数据(Ajax)请求、手动修改 DOM、localStorage 操作等),对标class组件里的生命周期
// useEffect可以拿到更新后的值,该 effect 会在每次组件更新(DOM更新)后执行
useEffect(() => {
document.title = num
})
return (
<>
<h1>{num}</h1>
<button onClick={() => setNum(num + 1)}>点击+1</button>
</>
)
}
export default App
tips:react的组件本质公式是UI = f(state),所以不需要副作用的操作,这些多余的操作都放在useEffect去进行
4.useEffect的生命周期
import React, { useEffect, useState } from 'react'
const App = () => {
const [num, setNum] = useState(0)
// useEffect对标class组件的三个生命周期:componentDidMount(挂载),componentDidUpdate(更新),componentWillUnmount(卸载)
// 也就是 挂载后,更新后,卸载后
// 1. 挂载后,useEffect在第二个参数上写上一个[],这样只在挂载时执行一次,后续不再执行
// useEffect(() => {
// console.log('挂载后');
// }, [])
// 2.挂载更新二合一,useEffect在第二个参数上写上 [依赖项],表示当依赖项变化时才出发更新,并且在挂载时也执行一次
// 效果相当于vue的watch开启立即更新
useEffect(() => {
console.log('更新后');
}, [num])
return (
<>
<h1>{num}</h1>
<button onClick={() => setNum(num + 1)}>点击+1</button>
</>
)
}
export default App
那么,useEffect卸载的生命周期该怎么写呢?它有点特殊,是和挂载后一起写
import React, { useEffect, useState } from 'react'
const App = () => {
const [show, setShow] = useState(true)
return (
<>
{show && <Child></Child>}
<button onClick={() => setShow(!show)}>点击卸载组件</button>
</>
)
}
const Child = () => {
// useEffect的挂载和卸载都写在同一个函数内,卸载操作的函数通过return返回,挂载的操作写在回调函数内
useEffect(() => {
// 挂载后开启页面改变事件监听
const resizeFn = () => console.log('页面刷新了')
window.addEventListener('resize', resizeFn)
// retrun返回的函数表示卸载时执行的函数
// 卸载时关闭监听事件
return () => {
window.removeEventListener('resize', resizeFn)
}
}, [])
return <h1>这是Child组件</h1>
}
export default App
tips:使用useEffect的推荐写法是一个useEffect对应一个功能,这样会方便维护和修改
5.useEffect里使用async await注意事项
import React, { useEffect, useState } from 'react'
import request from './utils/request'
export default function App() {
const [list, setList] = useState([])
// async await不能在useEffect里使用,要在外部定义函数去使用再到这里调用
useEffect(() => {
getList()
}, [])
// 发送请求(记住async await要在这里使用)
const getList = async () => {
const res = await request.get('/v1_0/channels')
setList(res.data.channels)
}
return (
<>
<ul>
{list.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
</>
)
}
总结:useEffect不能直接使用async await,要将函数写在外面,然后在useEffect里调用
二、Redux
1.什么是Redux
redux是状态管理工具,对标vue的vuex。redux解决了react多个组件传值时,组件间数据流混乱的问题。它可以集中存储和管理应用的状态;无视组件之间的层级关系传值;单向数据流,结构清晰
2.Redux基本用法
// 导入redux
import { legacy_createStore as createStore } from 'redux'
// 初始化store对象(通过参数默认值来初始化state的数据)
// 接受两个参数,第一个参数是state,第二个参数是action
const store = createStore((state = { name: 'zs', age: 18 }, action) => {
// 判断type是什么类型进行不同dispatch的操作
if (action.type === 'add') {
return { ...state, age: state.age + 8 }
}
if (action.type === 'des') {
return { ...state, age: state.age - 4 }
}
return state
})
// 调用store的dispatch方法时,要传入一个对象,对象里要具有type属性(唯一修改state数据的方法)
store.dispatch({ type: 'add' })
store.dispatch({ type: 'des' })
// subscribe可以用来跟踪state里的数据变化(放在subscribe之前的变化相当于未订阅,不会跟踪state的变化)
// subscribe的返回值是一个函数,用于取消跟踪
const unSubscribe = store.subscribe(() => console.log(store.getState()))
store.dispatch({ type: 'add' })
// 在这之后的数据变化不会被跟踪
unSubscribe()
store.dispatch({ type: 'add' })
store.dispatch({ type: 'add' })
优化写法
// 优化redux写法
import { legacy_createStore as createStore } from 'redux'
// 2.单独创建state初始值对象
const personState = {
name: 'zs',
age: 18
}
// 1.将初始化store里的函数单独创建( createStore(function) )
const personReducer = (state = personState, action) => {
// 3.使用switch case替换if else
switch (action.type) {
case 'add': return { ...state, age: state.age + 8 }
case 'des': return { ...state, age: state.age - 4 }
default: return state
}
}
// 正常初始化store对象,函数换成上方定义的函数
const store = createStore(personReducer)
export default store
总结:初始化一个redux分为四步
①导入redux
import { legacy_createStore as createStore } from 'redux'
② 初始化state对象
const initState = {}
③ 创建reducer函数
const personReducer = (state = initState, {type,payload}) => {
switch (type) {
case 'add': return { ...state, age: state.age + payload }
default: return state
}
}
④初始化store对象并导出
const store = createStore(personReducer)
export default store
3.Redux在react的基础应用
import { useEffect, useState } from 'react'
import store from './store'
export default function App() {
const [show, setShow] = useState(true)
return (
<div>
<h1>Redux基础案例</h1>
<button onClick={() => setShow(!show)}>点击切换卸载挂载</button>
{show ? <Son /> : null}
</div>
)
}
function Son() {
// 1.先将state里的count作为useState的count的初始值
const [count, setCount] = useState(store.getState().count)
// 3.因为redux不会更新视图,react引起视图变化只有setCount。
// 所以挂载后使用subscribe去监听state的count值变化,count发生变化就将新值赋值给setCount
useEffect(() => {
const unCount = store.subscribe(() => {
setCount(store.getState().count)
})
// 4. 卸载后取消监听
return () => {
unCount()
}
}, [])
return (
<div>
<h2>子组件</h2>
<h2>count:{count}</h2>
{/* 2.点击时调用store里的dispatch修改state的count值 */}
<button onClick={() => store.dispatch({ type: 'add' })}>+8</button>
<button onClick={() => store.dispatch({ type: 'des' })}>-4</button>
</div>
)
}
使用react-redux可以简化写法
import { useState } from 'react'
// 第二步,将useDispatch,useSelector方法从react-redux中导出
import { useDispatch, useSelector } from 'react-redux'
import addCount from './store/action/count'
export default function App() {
const [show, setShow] = useState(true)
return (
<div>
<h1>Redux基础案例</h1>
<button onClick={() => setShow(!show)}>点击切换卸载挂载</button>
{show ? <Son /> : null}
</div>
)
}
function Son() {
// 初始化变量使用useSelector(),参数是回调函数,里面传的是state的值
const count = useSelector((state) => state.count)
// 初始化方法使用useDispatch,直接调用即可
const dispatch = useDispatch()
return (
<div>
<h2>子组件</h2>
<h2>count:{count}</h2>
{/* 在action里可以传其他的参数来更加灵活的实现功能 */}
{/* 建议是新建一个文件夹维护这个方法,这个函数返回一个带有type字段的对象 */}
<button onClick={() => dispatch(addCount(8))}>+8</button>
<button onClick={() => dispatch(addCount(-4))}>-4</button>
</div>
)
}
总结:react-redux简化了useState的使用,不用像之前一样先绑定变量,再去使用store.subscribe监听变量的变化,然后修改变量引起视图更新。而是通过新的useSelector实现数据驱动视图,并且通过useDispatch简化了dispatch函数的调用
4.Redux的工作化拆分模块
当我们页面的需求变得很复杂时,所有页面的业务处理全部写在store中就会显得很臃肿,也就是在写屎山(笑)。所以模块化开发就变得很重要,规范自己的写法就可以让代码看上去清晰了然。
①首先将store进行拆分,拆分成action和reducer两个模块
store/index文件
import { applyMiddleware, legacy_createStore as createStore } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
// 所有模块的reducer函数的合并函数
import rootReducer from "./reducer";
// ceateStore(合并的reducer函数,composeWithDevTools(applyMiddleware(中间件1,...)))
// 工具包应用,应用包中间件
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk)))
export default store
②每个模块都action函数都分开存放,标识符全部设置为常量通过actionType保存(方便修改和使用)
action/actionType.js文件
export const CHANNEL_SAVE_LIST = 'channel/saveList'
export const CHANNEL_UPDATE_ID = 'channel/updateId'
export const NEWS_SAVE_NEWSLIST = 'news/saveNewsList'
③每个模块的reducer函数也分开保存,设置一个index.js文件来合并所有模块的reducer函数
reducer/index.js
import { combineReducers } from "redux";
import channelReducer from "./channelReducer";
import newsRecucer from "./newsReducer";
// combineReducers用于合并reducer函数,返回值为一个函数
// 语法: combineReducers({ 模块名:该模块的reducer函数 })
const rootReducer = combineReducers({
channel: channelReducer,
news: newsRecucer
})
export default rootReducer
三、补充知识
1.useRef的使用
import React, { useEffect, useRef, useState } from 'react'
export default function App() {
const [flag, setFlag] = useState(true)
return (<>
<div>{flag && <Child></Child>}</div>
<button onClick={() => setFlag(!flag)}>卸载组件</button>
</>
)
}
const Child = () => {
// 通过useRef创建ref对象
const timeRef = useRef()
useEffect(() => {
// ref不仅可以获取dom元素和组件,还可以当作全局变量使用
timeRef.current = setInterval(() => {
console.log(1);
}, 100)
}, [])
useEffect(() => {
return () => {
clearInterval(timeRef.current)
}
}, [])
return <>
<h1>子组件</h1>
</>
}
总结:useRef和之前的createRef相比,虽然功能大致一样,但是写法简化了许多。
2.useHistory和useLocation
import React from 'react'
import { BrowserRouter as Router, Route, Switch, useHistory, useLocation } from 'react-router-dom'
export default class App extends React.Component {
render() {
return (
<>
<Router>
<div className='router'>
<Switch>
<Route path="/l1" component={Luyou1}></Route>
<Route path="/l2" component={Luyou2}></Route>
</Switch>
</div>
<NotLuyou></NotLuyou>
</Router>
</>
)
}
}
function NotLuyou(props) {
// useHistory可以让没有配置路径的组件拥有history属性
const his = useHistory()
console.log('没有配置路径', his);
const loc = useLocation()
console.log('没有配置路径', loc);
return <h1>非路由组件</h1>;
}
function Luyou1(props) {
console.log(31, props.history.location);
return <h1>路由组件</h1>;
}
function Luyou2() {
return <h1>路由组件</h1>;
}
总结:useHistory和useLocation都可以让没有配置过路径的组件拥有history对象和location对象,比传统的需要配置路径才能在props属性上拥有history和location对象相比,显然前者功能更加强大
3.react解决样式冲突
①首先要将样式文件名改成xxx.module.scss(css或者less都行)
②在这个文件里,除了:global所包着的选择器,其他选择器都会被重命名,会给这个类名改成不重复的hash类名
// react写样式推荐写法:
// 根标签选择器让react的module去重命名,根标签里的子标签选择器不接受重命名(放在:global里)
.box {
background-color: orange;
:global {
.size {
font-size: 40px;
}
.size-big {
font-size: 100px;
}
}
}
4.逻辑复用
①自定义hooks(最推荐)
import React from 'react'
import { useState } from 'react';
export default function App() {
return (
<div>
<Header />
</div>
)
}
// 先定义一个自定义hooks函数(use开头)
const useMouse = () => {
const [mouse, setMouse] = useState({ x: 0, y: 0 })
const handleMouseMove = (e) => {
setMouse({ x: e.clientX, y: e.clientY })
}
return [mouse, handleMouseMove]
}
function Header() {
// 像之前使用useState一样,导入自定义hooks使用
const [mouse, handleMouseMove] = useMouse()
return (
<div onMouseMove={handleMouseMove}>
<h1>
x: {mouse.x} - y: {mouse.y}
</h1>
</div>
);
}
tips:自定义hooks只能使用在其他hooks和函数式组件中
②hoc(高阶组件)复用逻辑
import React from 'react'
export default function App() {
return (
<div>
<hr />
{/*最后通过标签形式渲染*/}
<LogMain />
<hr />
</div>
)
}
// hoc本质是一个函数,返回的是一个class类组件,类组件里render返回的是所传进来的组件
const logMouse = (Child) => {
// 返回class组件,逻辑在里面完成
return class Wrap extends React.Component {
state = {
mouse: { x: 0, y: 0 }
}
handleMouseMove = (e) => {
this.setState({ mouse: { x: e.clientX, y: e.clientY } })
}
render() {
// 将逻辑和状态通过父传子传给通过参数接收的组件
const { mouse } = this.state
return <Child mouse={mouse} handleMouseMove={this.handleMouseMove}></Child>
}
}
}
const Main = ({ mouse, handleMouseMove }) => {
return (
<div onMouseMove={handleMouseMove}>
<h1>
x:{mouse.x} - y:{mouse.y}
</h1>
</div>
);
}
// 因为返回的是一个组件,用变量去接收,最后通过标签形式渲染
const LogMain = logMouse(Main)
③renderProps逻辑复用(最不推荐)
import React from 'react'
import { useState } from 'react';
export default function App() {
return (
<div>
{/* 直接在属性上写jsx,在render上写的函数会在类组件里被调用 */}
<LogMouse render={(mouse, handleMouseMove) => {
return <>
<div onMouseMove={handleMouseMove}>
<h1>
x: {mouse.x} - y: {mouse.y}
</h1>
</div>
</>
}} />
</div>
)
}
// renderProps其实就是创建一个类组件,通过render调用传给props属性里的方法(我调用我自己属性上的方法)
class LogMouse extends React.Component {
// 正常在类组件里写逻辑
state = {
mouse: { x: 0, y: 0 }
}
handleMouseMove = (e) => {
this.setState({ mouse: { x: e.clientX, y: e.clientY } })
}
render() {
const { mouse } = this.state
// render返回不是传统的jsx,而是取调用props上自定义的render方法,并将状态和逻辑通过参数传给props上的render函数
return this.props.render(mouse, this.handleMouseMove)
}
}
tips:现在最新的react主流是通过自定义hooks去实现逻辑的复用,后续的两种都是之前没有出自定义hooks时类组件复用逻辑的方式
四、ts+react补充
1.props结合ts在react的使用
import React, { Component } from 'react';
export default class App extends Component {
render() {
return (
<>
<div>App</div>
<Child msg="zs" count={123}></Child>
</>
);
}
}
// 定义接口去描述类型(类似vue的props的对象写法)
interface IProps {
msg: string;
count?: number;
}
// 要在ts里使用props要先定义描述props对象的接口,再去传值
const Child = ({ msg, count }: IProps) => {
return <h1>Child</h1>;
};
2.ref结合ts
import React, { useRef } from 'react';
const App = () => {
// 创建ref对象要结合泛型一起使用
const iptRef = useRef<HTMLInputElement>(null);
return (
<>
<input ref={iptRef} type="text" />
<button onClick={() => iptRef.current?.focus()}>获取焦点</button>
</>
);
};
export default App;
五、其他补充
1.websocket
websocket本质其实就是一种通讯协议,它具有双向通信,长链接的特点。建立连接时,和http不同,websocket要附加一些身份认证信息,连接成功后双方就可以自由通信,,并且这个连接会持续存在直到某一方主动断开连接。主要用于聊天场景,webpack-dev-serve(热更新)
而http也是一种通讯协议,但它是单向通信,通信完立刻断开
2.react的鉴权路由
react的鉴权路由就是vue的路由守卫
// 鉴权路由
<Route path="/My/edit"
render={(data: any) => {
// 当token存在时才能访问
if (IsHaveAuth()) {
// 透传data
return <ProfileEdit {...data} />;
}
// token不存在时重定向到登录页
return <Redirect to="/login" />;
}}
原理就是通过renderProps,按照自定义的方式渲染组件
renderProps笔者前文有进行描述,此处就不多赘述
// 鉴权路由的封装
// 1.定义接口
interface IAuthRoute extends RouteProps {
path: string;
}
// component: Component表示重命名(因为组件名要大写开头)
export function AuthRoute({path,component: Component,...restProps: IAuthRoute)
{
// 如果不存在component组件,则不渲染
if (!Component) return null;
return (
<Route
// 透传props
{...restProps}
path={path}
render={(data: any) => {
if (IsHaveAuth()) {
// 透传data
return <Component {...data} />;
}
return <Redirect to="/login" />;
}}
/>
);
}
总结:封装组件其实最重要的是套皮+透传,先去定义一个组件实现封装单个路由的功能,再通过props传参和透传进行逻辑复用
3.响应拦截器前端处理三种状态
// 响应拦截器
request.interceptors.response.use(
function (response) {
return response.data;
},
function (error) {
// 1. 网络中断
if (!error.response) {
Toast.show({ content: '网络连接中断' });
}
// 2. 401(token过期)
if (error.response.status === 401) {
Toast.show({ content: '登录失效,请重新登录' });
// 跳转到登录页
history.push('/login');
// 这里加return就不用再写个else来实现非401错误
return Promise.reject(error);
}
// 3. 非401错误
Toast.show({ content: error.response.data.message });
return Promise.reject(error);
}
);
4.无感刷新
无感刷新其实就是用户token过期时,前端进行的一种处理。主要的原理就是当用户token过期时,此时后台返回两个token(用于刷新的token一般有效时长都会比token长),另一个token就是用来刷新的refresh_token。前端利用refresh_token去请求新的token,然后替换掉旧的token,再发起上一次失败的请求,就完成了无感刷新
// 拿出refresh_token
const { refresh_token } = getAuth();
// 1 有token无refresh_token
if (!refresh_token) {
Toast.show({ content: '登录失效,请重新登录' });
// 跳转到登录页
history.push('/login');
return Promise.reject(error);
}
// 2 有token也有refresh_token(无感刷新)
try {
// 发送拿新的refresh_token的请求(注意,这里不能用封装的request,因为请求头自动携带了token)
const res = await axios({
method: 'put',
url: baseURL + '/v1_0/authorizations',
headers: { Authorization: 'Bearer ' + refresh_token },
});
// 拿到返回来的新token
const newToken = res.data.data.token;
// 替换之前的token
setAuth({ token: newToken, refresh_token });
// 发起上次失败的请求并返回结果
return request(error.config);
} catch (err) {
// 3 token和refresh_token都过期,就让用户重新登录(利用try catch捕获这个错误)
Toast.show({ content: '登录失效,请重新登录' });
// 跳转到登录页
history.push('/login');
return Promise.reject(error);
}
6.函数式组件优化性能的三种方法
①React.memo
import React from 'react';
import { useState } from 'react';
const Test = () => {
const [count, setCount] = useState(0);
const [msg, setsetMsg] = useState('');
console.log('App更新了');
return (
<>
<div onClick={() => setCount(count + 1)}>改变数字</div>
<div onClick={() => setsetMsg(msg + 'A')}>改变字符串</div>
<PureCount count={count} />
<Msg msg={msg} />
</>
);
};
const Count = ({ count }: any) => {
console.log('count更新了');
return (
<>
<div>{count}</div>
</>
);
};
const Msg = ({ msg }: any) => {
console.log('msg更新了');
return (
<>
<div>{msg}</div>
</>
);
};
// 将组件作为参数,返回值作为标签使用
const PureCount = React.memo(Count);
export default Test;
React.memo类似类组件的PureComponent,都可以用来阻止无用更新
②useMemo
让我们先看看这段代码
import { useState } from 'react';
const Test = () => {
const [count, setCount] = useState(0);
const [msg, setsetMsg] = useState('');
const doubleCountFn = () => {
console.log('我被触发了,开始计算');
return count * 2;
};
const doubleCount = doubleCountFn();
return (
<>
<div onClick={() => setCount(count + 1)}>改变数字</div>
// 当改变字符串时,doubleCountFn也会被触发
<div onClick={() => setsetMsg(msg + 'A')}>改变字符串</div>
<h2>{doubleCount}</h2>
<Count count={count} />
<Msg msg={msg} />
</>
);
};
const Count = ({ count }: any) => {
return (
<>
<div>{count}</div>
</>
);
};
const Msg = ({ msg }: any) => {
return (
<>
<div>{msg}</div>
</>
);
};
export default Test;
我们不难发现,我们点击修改msg时也会触发doubleCountFn这个函数,这并不是我们想要的结果。
useMemo就可以避免这种情况的发生,它类似vue中的计算属性,具有缓存功能,只有影响计算结果的变量改变时采取计算。无关变量的改变不会去计算
import { useMemo, useState } from 'react';
const Test = () => {
const [count, setCount] = useState(0);
const [msg, setsetMsg] = useState('');
// 类似useEffect更新写法,useMemo接收两个参数,第一个是回调函数,第二个是依赖项
const doubleCount = useMemo(() => {
console.log('我被触发了,开始计算');
return count * 2;
}, [count]);
return (
<>
<div onClick={() => setCount(count + 1)}>改变数字</div>
<div onClick={() => setsetMsg(msg + 'A')}>改变字符串</div>
<h2>{doubleCount}</h2>
<Count count={count} />
<Msg msg={msg} />
</>
);
};
const Count = ({ count }: any) => {
return (
<>
<div>{count}</div>
</>
);
};
const Msg = ({ msg }: any) => {
return (
<>
<div>{msg}</div>
</>
);
};
export default Test;
这时改变msg的值就不会触发console.log的打印了
③useCallback
useCallback主要用于避免函数被重复定义,让我们看看下面的代码
import { useEffect, useState } from 'react';
const Test = () => {
const [count, setCount] = useState(0);
const handleCount = (num: number) => {
setCount(count + num);
};
return (
<>
<Count count={count} AddFn={handleCount} />
</>
);
};
const Count = ({ count, AddFn }: any) => {
useEffect(() => {
console.log('AddFn更新了');
}, [AddFn]);
return (
<>
<button onClick={() => AddFn(count + 2)}>点击修改count</button>
<div>{count}</div>
</>
);
};
export default Test;
我们发现,当修改count的值时,useEffect会被一直触发。这是因为每次修改count,组件都会被刷新,然后handleCount又重新声明,传给AddFn。地址发生改变,useEffect就会被触发。
使用useCallback就可以避免这种情况,它的语法和useMemo相同:useCallback(回调函数,[依赖项])
import { useCallback, useEffect, useState } from 'react';
const Test = () => {
const [count, setCount] = useState(0);
const handleCount = useCallback((num: number) => {
// 注意,此处不能直接使用count + 1,会产生闭包
setCount(num + 1);
// 此处不加依赖项
}, []);
return (
<>
<Count count={count} AddFn={handleCount} />
</>
);
};
const Count = ({ count, AddFn }: any) => {
useEffect(() => {
console.log('AddFn更新了');
}, [AddFn]);
return (
<>
<button onClick={() => AddFn(count + 2)}>点击修改count</button>
<div>{count}</div>
</>
);
};
export default Test;
和useMemo不同之处在于,useCallback最好不要加依赖项,否则又会重新声明了