我正在参与掘金创作者训练营第6期, 点击了解活动详情
前言
工作重要么?不重要。
那么我怎么会这么在意这份工作呢?或许菜是原罪,我应该时刻保持有跳槽的能力。
熬夜修仙
凌晨三点,我改完最后一个BUG,揉了揉发涩双睛,起身去煮了壶开水,站在窗边,看着这三更过后依旧灯火阑珊的深圳,回想起白天开会的场景:
- 产品: 这页面怎么加载那么慢?
- 主管:写代码首先要踩稳定义,定义踩稳写代码就会越来越轻松,越来越简单。
- 老板:这么简单的功能,怎么做那么久还做不出来?我开公司是为了赚钱的,不是慈善机构。
- 后端A:前端这接口多调了一次
- 我: ......
开局就背锅,作为一枚开发,我把锅理清楚后,分了一锅给后端,显而易见是接口慢是大锅我的是小锅。
背锅走人
根据问题,我定位到问题代码的位置。是个【useEffect】hook的坑,代码大概是这样子的:
import "./App.css";
import { useEffect, useState } from "react";
import { Button } from "antd";
function App() {
const [data, setData] = useState({});
const [code, setCode] = useState(1);
useEffect(() => {
console.log("init", code);
getData();
}, [code]);
const getData = () => {
// 模拟数据请求
const promise = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve({
name: "小白",
age: 18,
});
}, 1000);
});
promise.then((res: any) => {
setData(res);
});
};
return (
<div>
<Button
onClick={() => {
setCode((num: number) => num + 1);
}}
>
code++
</Button>
<div> data: {JSON.stringify(data)}</div>
<div> code: {code}</div>
</div>
);
}
export default App;
在控制台log:
我们发现init日志调用了两次。
需求是这样子的:当code改变时候调用请求,初始化不需要调用。
我们来分析下代码:
我们在 useEffect 传递第二个参数,它是 effect 所依赖的值数组,一旦 effect 的依赖发生变化,它就会被触发。但是默认情况下,effect 会在每轮组件渲染完成后都会执行。
结论:
使用useEffect 监听变化不是最佳的选择。
重学useState
我门来复习下useState的返回第二个参数 setXXX与 class 类的this.setState的区别:
参数对比
-
this.setState( updata , callback)
- updata:any/function - 用于更新数据
- callback:function - 用于获取更新后最新的 state 值
-
setXXX( updata )
-
updata:any/function - 用于更新数据
-
从参数中可以看出setXXX没有第二个参数,加着setXXX与this.setState都为异步,更新 state 后无法直接获取最新的 state 值
// 在class中
this.setState({name:'ls'})
console.log(this.state.name) //打印的是之前的旧值,而非'ls'
// 在useState中
setData({name:'ls'})
console.log(data.name) //打印的是之前的旧值,而非'ls'
所以在函数组件中,常用以下方法解决,所以有人上文中的BUG:
const [count, setCount] = useState(1);
setCount(count + 1);
console.log(count) //setCount()后直接打印出的是旧数据
解决办法:由于useState()获取的setState()不支持第2个参数,可以通过useEffect来解决此问题
useEffect(() => {
console.log(count);
}, [count]);
怎么解?(useStateCallback)
其实解法有许多,在这里我给出我自己的答案。
第1版羊头与狗肉
import { useState } from "react";
export const useStateCallback = (initData: any) => {
const [state, setState] = useState(initData);
const run = (data: any, fn?: Function) => {
setState(data);
// 接收第二个参数,将数据返回
fn && fn(data);
};
// 返回参数
return [state, run];
};
可能有些大佬一眼就看出了问题,我们是要改变后的数据,现在这数据其实并不是我们想要的。
第2版
import { useState, useEffect, useRef } from "react";
export const useStateCallback = (initData: any) => {
const [state, setState] = useState(initData);
const fn = useRef<any>();
useEffect(() => {
console.log(state, "useStateCallback");
fn.current && fn.current();
}, [state]);
const run = (data: any, fn1?: Function) => {
fn.current = fn1;
setState(data);
};
// 返回参数
return [state, run];
};
简单分析下代码:
我们定义了个变量fn保存回调函数,在useEffect中执行回调。
我们来测试下:
import "./App.css";
import { useEffect, useState } from "react";
import { Button } from "antd";
import { useStateCallback } from "./hooks/useStateCallback";
function App() {
const [data, setData] = useState({});
const [code, setCode] = useStateCallback(1);
useEffect(() => {
console.log(code, "useEffect");
}, [code]);
const getData = () => {
console.log("code:", code);
const promise = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve({
name: "小白",
age: 18,
});
}, 1000);
});
promise.then((res: any) => {
setData(res);
});
};
return (
<div>
<Button
onClick={() => {
setCode(
(num: number) => num + 1,
() => {
getData();
}
);
}}
>
code++
</Button>
<div> data: {JSON.stringify(data)}</div>
<div> code: {code}</div>
</div>
);
}
export default App;
当我们点击buttom时log是这样的:
分析执行结果:
在useStateCallback发出更新之后立马执行回调函数并不能得到最新结果,最新结果需要在父组件中useEffect中获取。
结论:
hook不满足需求。
第3版羊头与狗肉真香
退而求之,在useStateCallback时候以参数的形式返回。
import { useState, useEffect, useRef } from "react";
export const useStateCallback = (initData: any) => {
const [state, setState] = useState(initData);
const fn = useRef<any>();
useEffect(() => {
console.log(state, "useStateCallback");
// 在这里以参数的形式返回
fn.current && fn.current(state);
}, [state]);
const run = (data: any, fn1?: Function) => {
fn.current = fn1;
setState(data);
};
// 返回参数
return [state, run];
};
最终代码:
import "./App.css";
import { useState } from "react";
import { Button } from "antd";
import { useStateCallback } from "./hooks/useStateCallback";
function App() {
const [data, setData] = useState({});
const [code, setCode] = useStateCallback(1);
const getData = (code: number) => {
console.log("code:", code);
const promise = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve({
name: "小白",
age: 18,
});
}, 1000);
});
promise.then((res: any) => {
setData(res);
});
};
return (
<div>
<Button
onClick={() => {
setCode(
(num: number) => num + 1,
(code: number) => {
getData(code);
}
);
}}
>
code++
</Button>
<div> data: {JSON.stringify(data)}</div>
<div> code: {code}</div>
</div>
);
}
export default App;
噩梦
烧开的水壶把我的思绪打断,一阵冷风袭来,吓得我一哆嗦,突然远处传来跑车的声音,我知道那是我的噩梦,以前熟睡的时候经常被这怪声吵醒,它是我的噩梦,也是别人的梦想,以后打死不租那么近马路的房子。