再此之前你已经学习了:
React18(函数式开发)+Ts入门开发(一)创建Ts项目使用SCSS、antdUI库
React18(函数式开发)+Ts入门开发(二)路由配置以及路由的基础使用
今天第三讲就讲讲复制传值以及hooks的实际应用吧。
PS:此页面我已经事先创建好,并且建好路由了。不会创建页面建路由的看上面一二两篇。本篇就不重复讲述怎么创建页面和路由了。
一 初识useEffect,先简单介绍下它是什么
为什么React18后函数式开发火了,就是因为18之前函数式组件是没有生命周期的,导致它不能满足某些特殊需求。而有了hooks
后,他就有了自己的生命周期。
当然,我这里只是简单介绍一下以及我们开发常用的。更详细的需要看官方文档了。
1. 模拟componentDidMount第一次渲染
useEffect(() => {
console.log('');
}, []) // 第二个参数为空时只会在第一次渲染时执行
2. 模拟componentDidUpdate
useEffect(() => {
console.log('n变化了');
}, [n]) // 第二个参数为要监听的数据
// 不传第二个参数,则会在 state 的任意一个属性改变时都会触发该函数回调
useEffect(() => {
console.log('任意属性变化');
})
3. 模拟componentWillUnmount
useEffect(() => {
console.log('任意属性变了');
return () => {
console.log('该组件要销毁了');
}
})
二 初识useState 我们先声明两个变量,并且赋值。以及useEffect 的大概使用
1. 基础赋值
import { useState, useEffect } from "react";
// 父组件
function HooksDemo() {
// 定义一个 num 初始为 0
// 定义一个 isBoolean 初始为 false
const [num, setNum] = useState(0);
const [isBoolean,setBoolean] = useState(false);
// 等同于Vue
// data() {
// return {
// num:0,
// isBoolean:false,
// }
// }
// 初始化执行
useEffect(()=>{
setNum(5);
console.log(num); // 你以为是输出5,实际是 0 也就是上一次的值
setBoolean(true)
console.log(isBoolean); // 你以为是输出true,实际是 false 也就是上一次的值
},[]);
// 等同于Vue
// mounted(){
// this.num = 5;
// console.log(num); // 输出5
// this.isBoolean = true;
// console.log(num); // 输出true
// }
return <div className="home">hooksDemo 示例</div>;
}
export default HooksDemo;
2. 其他赋值
有时候我们可能定义的是一个对象,或者其他的,只需要改变其中的某一个值。并不需要全部改变。
这种情况怎么办呢?
const [user,setUser] = useState({name:"zs",age:20});
// demo1
setUser(Object.assign(user,{age:18})); // {name: 'zs', age: 18}
// demo2
const changeUser = (num: number) => {
setUser((state) => {
let { name, age } = state;
age = num;
return {
name,
age,
};
});
};
三 通过useCallback同步获取Set后的值
从上面我们发现一个问题,就是当我们set值后,无法同步获取。其实React中的setState本身执行的过程和代码是同步的(比如你HTML里{num} 显示的就是5)
,只是因为 React 框架本身的性能优化机制而导致的。
React 中合成事件和生命周期函数的调用顺序在更新之前,导致在合成事件和生命周期函数中无法立刻得到更新后的值,形成了异步的形式。说白了就是你无法立即获取最新的值。也就是set后的值。下面看示例。
示例一
// css
//.active {
// color: red;
//}
// 父组件
function HooksDemo() {
// 定义一个 tabList
const [tabList] = useState([
{
name: "全部",
code: "all",
},
{
name: "已开启",
code: "open",
},
{
name: "已关闭",
code: "close",
},
]);
const [cutTab, setCutTat] = useState("");
useEffect(() => {
// 这里不要问为什么不初始化 useState("all"); 实际开发中这个值可能是其他组件传过来的。
setCutTat("all");
console.log(cutTab,'看输出'); // 输出 ""
}, []);
return (
<div className="home">
<ul>
{tabList.map((tab, inx) => {
return <li key={inx} className={`${cutTab == tab.code ? 'active': ''}`}>{tab.name}</li>;
})}
</ul>
</div>
);
}
export default HooksDemo;
image.png
此时,初始化的时候
console.log(cutTab,'看输出'); // 输出 ""
,但是页面实际却高亮第一个了。这就我上面说的它再执行的过程实际是同步的。只是由于机制问题,你输出的时候他就变成异步了。那么此时会出现什么问题呢?我们看下一个示例
示例二
import "./index.scss";
import { useState, useEffect } from "react";
// 父组件
function HooksDemo() {
// 定义一个 tabList
const [tabList] = useState([
{
name: "全部",
code: "all",
},
{
name: "已开启",
code: "open",
},
{
name: "已关闭",
code: "close",
},
]);
const [cutTab, setCutTat] = useState("");
useEffect(() => {
// 这里不要问为什么不初始化 useState("all"); 实际开发中这个值可能是其他组件传过来的。
setCutTat("all");
console.log(cutTab, "看输出"); // 输出 ""
}, []);
// 页签切换
const tabChange = (tab: any) => {
if (tab.code == cutTab) {
return;
}
setCutTat(tab.code);
// 通过请求获取数据
getListData();
};
// 请求获取数据
const getListData = () => {
// 此时拿到的 code 其实是上一次的。
console.log(cutTab) // 输出的是上一次的。
};
return (
<div className="home">
<ul>
{tabList.map((tab, inx) => {
return (
<li
key={inx}
className={`${cutTab == tab.code ? "active" : ""}`}
onClick={(e) => tabChange(tab)}
>
{tab.name}
</li>
);
})}
</ul>
</div>
);
}
export default HooksDemo;
当然有的朋友就要问了,我为什么要setCutTat(tab.code);
后再掉接口,不能直接掉接口getListData(tab.code);
把参数传过去吗?答案是可以的。我这里只是演示某种特殊的需求。
好,入正题;此时点击获取的是上一次值,我总不能把上一次的值传给后台掉接口吧。下面就给大家提供下解决方案。
四 初始useCallback,以及大概使用。
useCallback返回一个 memoized
回调函数。
把内联回调函数及依赖项数组作为参数传入 useCallback
,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate
)的子组件时,它将非常有用。
更具体说明看官网介绍吧。
我这里就不多阐述了,直接讲使用。
1. 新建 useSyncCallback.tsx
// useSyncCallback.tsx
import { useEffect, useState, useCallback } from "react";
const useSyncCallback = (callback: any) => {
const [proxyState, setProxyState] = useState({ current: false });
const [params, setParams] = useState([]);
const Func = useCallback(
(...args: []) => {
setParams(args);
setProxyState({ current: true });
},
[proxyState]
);
useEffect(() => {
if (proxyState.current === true) setProxyState({ current: false });
}, [proxyState]);
useEffect(() => {
proxyState.current && callback(...params);
});
return Func;
};
export default useSyncCallback;
2. 我的页面引入,并使用它,替换掉原方法
import useSyncCallback from "./utils/useSyncCallback";
// 请求获取数据
// const getListData = () => {
// // 此时拿到的 code 其实是上一次的。
// console.log(cutTab) // 输出的是上一次的。
// };
const getListData = useSyncCallback(() => {
console.log(cutTab) // 此时就是最新的值了
})
今天的干货就讲到这了,下一篇讲讲父子通讯
、兄弟通讯
、以及useEffect(()=>{},[n])
的基础使用吧。
网友评论