阅读 70

Vue转React随记

前言

  • 从技术栈Vue转为React是很常见的情况,本文就该阶段做一个汇总记录。

  • 直接学习React的新人开发者也可作为参考。

  • 本文中的内容大多为个人碰到的问题或者思考。这其中绝大部分在官网都有说明。

Vue到React

本文只记录最重要,最明显的直观差异。如果想要详细的功能对比,推荐大家看这篇文章。

  • 如果你使用过Vue的渲染函数和JSX,这部分内容和React是相似的。而在React中这部分的比例更重。因为Vue大多数时候推荐使用模板语法。但要注意只是相似,JSX编译并不完全一样。差异点查看官网这里。

  • React不会自动帮你更新视图,组件内部数据变更后,需要手动setState。如果传入子组件的props发生变化,子组件会触发重新渲染。

  • React中的props比Vue更灵活。几乎可以看做是Vue的props + slot。

常见问题

引发死循环

1. 组件中直接调用会触发re-render的逻辑

export const MyComponent = (props) => {   const [t, setT] = useState(0);   // 每次组件渲染都会重新走到这里,setState又重新触发组件渲染   setT((o) => o + 1);   return <h1>{t}</h1>; }; 复制代码

2. useEffect无依赖时调用re-render的逻辑(和1类似)

export const MyComponent = (props) => {   const [t, setT] = useState(0);   // 因为无依赖,组件每次渲染完成后会进入useEffect的回调,setState又重新触发组件渲染   useEffect(() => {     setT((o) => o + 1);   });   return <h1>{t}</h1>; }; 复制代码

3. 不恰当设置props默认值

这一点是最隐蔽,最容易忽视的,一定要非常小心。

export const MyComponent = (props) => {   // arr是一个引用类型,引用类型提供默认值,每一次渲染时都赋了一个不同的地址引用。   const { arr = [] } = props;   const [t, setT] = useState(0);   // useEffet会认为props arr发生了变化,所以每次渲染都会进入回调,实际上和情况2一样了   useEffect(() => {     setT((o) => o + 1);   }, [arr]);   return <h1>{t}</h1>; }; 复制代码

函数中取到的值未更新

1. setState异步更新

export const MyComponent = (props) => {   const [t, setT] = useState(0);   useEffect(() => {     setT((o) => o + 1);     // setState后马上同步获取,获取到的为更新前的值     console.log(t, 't'); // 0   }, []);   return <h1>{t}</h1>; }; 复制代码

2. Hooks闭包陷阱

对于setState异步更新中的例子,再来扩展一下,可以发现useState的闭包问题。

export const MyComponent = (props) => {   const [t, setT] = useState(0);   useEffect(() => {     setT((o) => o + 1);     // 既然是时序问题,就强行延后执行state获取操作,会发现依然取不到更新后的state     setTimeout(() => {       console.log(t, 't0'); // 0     }, 1000);   }, []);   return <h1>{t}</h1>; }; 复制代码

  • 产生原因:简单的讲,例子中的effect只会在首次渲染完后被执行,effect函数在执行时创建自己的执行上下文,变量t因为闭包的原因一直存在于该执行上下文中,当第二次渲染,t更新了,实际上和effect中的t在内存中是两个变量。如果在依赖列表中加入t,useEffect内部会自动更新变量t。

3. 解决方法

问题1、2的详细原因和完善的解决方案,可参考这篇文章。这里给出个人最常用的解决方案。

  • 在useEffect中,如果逻辑允许,可以把变量加入到依赖列表中。比如对于上述例子,如果你只需要获取t,并且打印,就可以将其加入到依赖列表。而例子中即要get,又要set。如果加入到依赖列表会引发死循环

  • 使用useRef。useRef创建的变量贯穿于组件整个生命周期。所以不存在闭包问题。

容易忽视的概念

这部分是个人在通读官网后,发现和我之前理解存在偏差的一些概念。

useEffect的清除时机

  1. 执行下一个 effect 之前,上一个 effect 就已被清除。即useEffect的回调被执行前,清除上一次的回调。

export const MyComponent = (props) => {   const [t, setT] = useState(0);   useEffect(() => {     if (t < 5) {       setT((o) => o + 1);     }     // 在return的函数中执行清除的逻辑     return () => {       console.log(t); //打印了5次,0 1 2 3 4     };   });   return <h1 onClick={() => {}}>{t}</h1>; }; 复制代码

  1. 组件卸载时一定会调用一次清除。这也是为什么能够使用依赖为空数组的useEffect代替组件卸载的生命周期

JSX对于不同数据结构的处理方式

  • 布尔类型、Null 以及 Undefined 将会忽略

// 可以放心大胆的写如下判断代码,不必担心true或者undefined被渲染到页面上。但要小心0和NaN {t && <h1>{t}</h1>} 复制代码

  • 数组会被解构渲染,即按顺序渲染数组中的每一项

动态组件

如果某个场景下的组件需要动态渲染,可以有两个方案。

  • 先将组件名赋值给一个大写的变量,必须大写,然后使用JSX渲染。这里展示官网的例子

import React from 'react'; import { PhotoStory, VideoStory } from './stories'; const components = {   photo: PhotoStory,   video: VideoStory }; function Story(props) {   // 正确!JSX 类型可以是大写字母开头的变量。   const SpecificStory = components[props.storyType];   return <SpecificStory story={props.story} />; } 复制代码

  • 直接使用createElement方法创建组件,该方法的第一个参数可以使用正常的变量。所以改造官网例子。

import React, { createElement } from 'react'; import { PhotoStory, VideoStory } from './stories'; const components = {   photo: PhotoStory,   video: VideoStory }; function Story(props) {   // c为js变量即可   const c = components[props.storyType];   return createElement(c, { story: props.story }); }


作者:萌鱼
链接:https://juejin.cn/post/7171980371687374879

文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐