当前找工作环境恶劣,很多求职者表示招聘不仅要求会 vue,还要求会 react。
刚好这段时间我用 React18 + Vite + TS 为公司从 0 到 1 开发了一个项目,就顺便总结了一些前端开发知识和技巧,帮助我记忆这些知识的同时,希望能对你也有所启发。欢迎评论区交流。
我会把代码(当然不是真实项目的。。)上传到 GitHub 上,文末可以取链接。最后还请多多评论、点赞支持!废话不多说,下面开始进入正题:
创建项目
选择 React + TypeScript 模板。不要选用 React + TypeScript + SWC。因为 SWC 不支持 babel 插件,不能转换 es6 语法和特性。这个后面也会说到。
pnpm create vite复制代码
如何配置
根据官网的说明,只需要简单的执行下面的命令,然后根据你的项目进行选择配置。
pnpm create @eslint/config复制代码
让 eslint 检查缩进
如果我想让所有代码的索引为 2 个空格,否则就报错误,那么如何配置呢?
在 .eslintrc.cjs 文件中。
module.exports = { rules: { // 缩进必须为 2 个空格 https://eslint.org/docs/latest/rules/indent#rule-details "indent": ['error', 2], // 禁止所有 tab https://eslint.org/docs/latest/rules/no-tabs#rule-details 'no-tabs': 'error', }}复制代码
禁用 tab 表示缩进使用 spaces,而不是 tab。可以在 vscode 右下角查看配置。
使用 eslint-plugin-react-hooks 插件
这个插件是在上面那个清单里看到的。可以检查我们写的 react hook 是否规范。
add eslint-plugin-react-hooks --dev复制代码
然后扩展 eslint 配置
{ "extends": [ // ... "plugin:react-hooks/recommended" ]}复制代码
TypeScript 技巧
怎么定义全局类型
比如我想在全局都能使用以下几个类型
type pageview = 'pageview'type click = 'click'type blockview = 'blockview'type elementview = 'elementview'复制代码
就在 global.d.ts 文件里进行定义,然后就可以在全局里使用啦。但是要确保 global.d.ts 文件包含在 tsconfig.json 文件的 include 选项中。
declare global { declare type pageview = 'pageview' declare type click = 'click' declare type blockview = 'blockview' declare type elementview = 'elementview'}复制代码
如何获取数组类型的元素类型
type ListType = {a:number,b:string}[]const list = [{}] as ListType// 法一type ArrayItem<T> = T extends Array<infer R> ? R : nevertype Item = ArrayItem<ListType>// 法二TaskList['task'][number]type Item = ListType[number]复制代码
setState 传入回调函数场景
假设 age 为 42,这个方法将会调用 setAge(age + 1) 三次。
function handleClick() { setAge(age + 1); // setAge(42 + 1) setAge(age + 1); // setAge(42 + 1) setAge(age + 1); // setAge(42 + 1)}复制代码
然而,触发 handleClick 方法后,age 还是 43,不是 45。因为 set 方法不会更新 age 状态变量在当前正在运行的代码。所以每次 setAge(age + 1) 都变成 setAge(43)。
为了解决这个问题,你可以传一个函数类型参数给 setAge,获取到下一会的状态。
function handleClick() { setAge(a => a + 1); // setAge(42 => 43) setAge(a => a + 1); // setAge(43 => 44) setAge(a => a + 1); // setAge(44 => 45)}复制代码
a => a + 1
是你的更新函数。它接收一个待改变的 state,然后在函数体计算返回下一个 state。
React 把更新函数放入一个队列中。然后在下一次 render 时,更新函数将会以相同的顺序被调用。
- a => a + 1 将接收 42 作为待改变的状态,然后返回 43 作为下一次的状态。
- a => a + 1 将接收 43 作为待改变的状态,然后返回 44 作为下一次的状态。
- a => a + 1 将接收 44 作为待改变的状态,然后返回 45 作为下一次的状态。
- 没有其它队列需要更新, 最后 React 将会存储 45 作为最后的状态。
在开发模式下,React 可能会调用两次你的更新函数,目的是保他们是纯的没有副作用。
理解 useRef、useMemo、useCallback
useRef 可用来存储一个引用值(不会受 re-render 影响),也可用来获取 dom 节点。
useMemo 用来缓存一个值。当依赖项为空数组时,缓存值永远不会变。当有依赖性时,每次 re-render 如果依赖改变,那么将重新执行函数,将新的函数返回值作为缓存的数据。
useCallback 是 useMemo 的语法糖,相当于返回一个函数。
const fn1 = useCallback(() => { console.log(123); }, []) const fn2 = useMemo(() => () => { console.log(123); }, [])复制代码
react 不会缓存组件状态的解决方案
react 不像 vue 能使用 keep-alive。
当 react 跳到另外一个页面,再返回到上一个页面。上一个页面会重新渲染。
由于水平有限,所以决定将接口数据进行缓存。第一次没有缓存会请求。第二次执行方法时判断是否有缓存,有就直接返回缓存的数据。
我觉得要理解这个 hook,要明白模块只会被加载一次。还要明白每次 re-render 有些方法是会被重新执行,有些方法是会被重新定义。
useCache hook
interface Cb { (...arg: unknown[]): unknown}const cacheMap = new Map();export default (key: string, callback: Cb) => { return async (cache = true) => { const result = cacheMap.get(key) if(cache && result) { return result } const res = await callback() cacheMap.set(key, res) return res }}复制代码
使用
function App () { const [count, setCount] = useState(0) useEffect(() => { fn() }) async function fn () { const res = await getStatus() console.log('===>', res); } const getStatus = useCache('getStatus', () => { const arr = [1,2,3,4] const res = [] as number[] arr.forEach(num => { console.log(num); res.push(num) }); return res }) function add () { setCount(c => c + 1) } return ( <> <h2>==</h2> <br /> {count} <br /> <button onClick={add}>add</button> <h2>==</h2> </> )}复制代码
react-router-dom 简单使用
页面跳转管理 使用 react-router-dom
暂无评论内容