Vue 转 React的开发者,常犯的10个错误~

大厂技术  高级前端  Node进阶
点击上方 程序员成长指北,关注公众号
回复1,加入高级Node交流群

前言


作者:_你当像鸟飞往你的山
链接:https://juejin.cn/post/7220043684293083195
来源:稀土掘金

1、用 0 做条件渲染

这可能是很多新手都遇到过的问题了吧!鄙人当年也犯过这个错误,但你说它是错误,也可以说是 react 的一个坑:0 是假值,却不能做条件渲染。

看下面这个例子:

2e563f946dc85cd5a15b4a0bb4dcb1dc.jpeg
ray-so-export (11).png

可能你想当然他会在 items 为空数组的时候显示 ShoppingList 组件。但实际上却显示了一个 0!这是因为 0 在 JavaScript 中是一个假值,&& 操作符短路了,整个表达式被解析为 0。它就等价于:

function App() {
  return (
    <div>
      {0}
    </div>
  );
}
复制代码

与其他假值(''nullfalse 等)不同,数字 0 在 JSX 中是一个有效值。毕竟,在很多情况下,我们确实想打印数字 0!

正确的做法是:

function App() {
  const [items, setItems] = React.useState([]);
  
  return (
    <div>
      {items.length > 0 && (
        <ShoppingList items={items} />
      )}
    </div>
  );
}

// 或者用三元运算符
function App() {
  const [items, setItems] = React.useState([]);
  
  return (
    <div>
      {items.length
        ? <ShoppingList items={items} />
        : null}
    </div>
  );
}
复制代码

2、突变状态

先来看这个常见的页面:

48e155582cebde8957e946472e43ebf2.jpeg
image.png

代码:

1c7152d04660ba5ac60b3869672b67d3.jpeg
ray-so-export (12).png

每当增加一个新项目时,handleAddItem 函数就会被调用。但是,它并不起作用!当我们输入一个项目并提交表单时,该项目没有被添加到购物清单中。

问题就在于我们违反了也许是 React 中最核心的原则 —— 不可变状态。React依靠一个状态变量的地址来判断状态是否发生了变化。当我们把一个项目推入一个数组时,我们并没有改变该数组的地址,所以 React 无法判断该值已经改变。

正确的做法是:

function handleAddItem(value) {
  const nextItems = [...items, value];
  setItems(nextItems);
}
复制代码

不建议修改一个现有的数组,而是从头开始创建一个新的数组。这里的区别在于编辑一个现有的数组和创建一个新的数组之间的区别。

同样的,对于对象类型的数据也是:

// ❌ 不建议
function handleChangeEmail(nextEmail) {
  user.email = nextEmail;
  setUser(user);
}

// ✅ 推荐
function handleChangeEmail(email) {
  const nextUser = { ...user, email: nextEmail };
  setUser(nextUser);
}
复制代码

为什么react不推荐突变状态

  • 调试:如果你使用console.log 并且不改变状态,你过去的日志将不会被最近的状态破坏修改,你可以清楚的看到渲染之间的状态变化

  • 优化:如果之前的propsstate和下一个状态相同,常见的react优化策略将会跳过本次渲染,如果你从不改变状态,检查变化就会非常的块,如果prevProps === props,react就可以确定它内部并没有发生变化

  • 新功能:react正在构建的新功能依赖将状态视为快照,如果你正在更新过去的状态版本,这会导致无法使用新功能

  • 需求变更:一些需要撤销/重做和显示历史记录的值,在没有突变的情况下更容易执行,这是因为你可以将过去的值保存在副本中,并在适用的情况下重做他们

  • 更简单的实现:因为react不依赖突变,所以它不需要对你的对象做任何处理,不需要劫持你的对象。总是将它们包装到代理中,或者在初始化时像许多“反应式”解决方案那样做其他工作。这也是为什么 react 允许您将任何对象置于状态(无论有多大)而没有额外的性能或正确性陷阱。

3、唯一的 key

你肯定经常会在控制台看到一个警告:

Warning: Each child in a list should have a unique "key" prop.
复制代码

比如:

4a090b9b59cbf50527917acc50ee2291.jpeg
ray-so-export (13).png

控制台就会报警告:

95ee97ab2d377bd0e3cdfc33e9047537.jpeg
image.png

每当我们渲染一个元素数组时,我们需要向React提供一些额外的上下文,以便它能够识别每一个项目,通常就是需要一个唯一的标识符。

你可以这么做:

function ShoppingList({ items }) {
  return (
    <ul>
      {items.map((item, index) => {
        return (
          <li key={item}>{item}</li>
        );
      })}
    </ul>
  );
}
复制代码

但这并不是个推荐的做法。这种方法有时会奏效,但在有些情况下会造成一些相当大的问题。随着你对React的工作原理有了更深的了解,你就能根据具体情况来判断它是否没问题。

有一种绝对安全的方式来解决这个问题:

function handleAddItem(value) {
    const nextItem = {
        id: crypto.randomUUID(),
        label: value,
    };

    const nextItems = [...items, nextItem];
    setItems(nextItems);
}
复制代码

crypto.randomUUID 是一个内置于浏览器的方法(它不是一个第三方包)。它在所有主要浏览器中都可用。这个方法会生成一个独特的字符串,比如:d9bb3c4c-0459-48b9-a94c-7ca3963f7bd0。通过在用户提交表单时动态生成一个ID,我们保证了购物清单中的每一个项目都有一个唯一的ID。

所以,更好的做法是:

function ShoppingList({ items }) {
  return (
    <ul>
      {items.map((item, index) => {
        return (
          <li key={item.id}>
            {item.label}
          </li>
        );
      })}
    </ul>
  );
}
复制代码

你也最好不要耍小聪明,图方便,这么做:

// ❌ 不建议这么做
<li key={crypto.randomUUID()}>
  {item.label}
</li>
复制代码

像这样在 JSX 中生成它将导致 key 在每次渲染时都会改变。每当 key 发生变化时,React 就会销毁并重新创建这些元素,这对性能会产生很大的负面影响。

这种模式,在第一次创建数据时生成 key,可以应用于各种情况。例如,这里是我从服务器获取数据时创建唯一ID的方法:

async function retrieveData() {
  const res = await fetch('/api/data');
  const json = await res.json();
  
  const dataWithId = json.data.map(item => {
    return {
      ...item,
      id: crypto.randomUUID(),
    };
  });
  
  setData(dataWithId);
}
复制代码

4、空格缺失

你肯定遇到过这种场景:

import React from 'react';

function App() {
  return (
    <p>
      Welcome to the new world!
      <a href="/login">Log in to continue</a>
    </p>
  );
}

export default App;
复制代码
676aa70ea1f7dc162995940b19dfe87b.jpeg
image.png

注意“login in”前面,是没有空格的:

137789754bc4181b855cb9368ea391a9.jpeg
image.png

这是因为 JSX编译器(将我们编写的JSX转化为对浏览器友好的JavaScript的工具)无法真正区分语法上的空白和我们为缩进/代码可读性而添加的空白。

正确的做法是:

<p>
  Welcome to the new world!
  {' '}
  <a href="/login">Log in to continue</a>
</p>
复制代码

tips:如果你使用 Prettier,它会自动为你添加这些空格字符!

5、改变状态后访问状态

这恐怕是react新手最常犯的错了吧:

import React from 'react';

function App() {
  const [count, setCount] = React.useState(0);
  
  function handleClick() {
    setCount(count + 1);
    
    console.log({ count });
  }
  
  return (
    <button onClick={handleClick}>
      {count}
    </button>
  );
}

export default App;
复制代码

如果非要取怎么办呢?有办法:

function handleClick() {
  const nextCount = count + 1;
  setCount(nextCount);
  
  console.log({ nextCount });
}
复制代码

6、返回多个元素

有时,一个组件需要返回多个顶层元素。

function LabeledInput({ id, label, ...delegated }) {
  return (
    <label htmlFor={id}>
      {label}
    </label>
    <input
      id={id}
      {...delegated}
    />
  );
}

export default LabeledInput;
复制代码
58660c099d6067f1b7ca0dfea3f466b0.jpeg
image.png

我们希望我们的 LabeledInput 组件能够返回两个元素:一个<label>和一个<input>。发生这种情况是因为JSX被编译成普通的JavaScript后是这样子:

function LabeledInput({ id, label, ...delegated }) {
  return (
    React.createElement('label', { htmlFor: id }, label)
    React.createElement('input', { id: id, ...delegated })
  );
}
复制代码

在JavaScript中,我们不允许像这样返回多个东西。这也是这个方法不可行的原因,就好比:

function addTwoNumbers(a, b) {
  return (
    "the answer is"
    a + b
  );
}
复制代码

正确的做法是:

function LabeledInput({ id, label, ...delegated }) {
  return (
    <>
      <label htmlFor={id}>
        {label}
      </label>
      <input
        id={id}
        {...delegated}
      />
    </>
  );
}
复制代码

7、非受控到受控的切换

来看一个比较典型的表单场景,将一个输入与一个React状态绑定:

import React from 'react';

function App() {
  const [email, setEmail] = React.useState();
  
  return (
    <form>
      <label htmlFor="email-input">
        Email address
      </label>
      <input
        id="email-input"
        type="email"
        value={email}
        onChange={event => setEmail(event.target.value)}
      />
    </form>
  );
}

export default App;
复制代码

如果你开始键入,你会注意到一个控制台警告:

c3dcf9eb22272e8921102863a65e2e24.jpeg
image.png

怎么解决这个问题?我们需要将我们的状态初始化为一个空字符串:

const [email, setEmail] = React.useState('');
复制代码

当我们设置了 value 属性时,等于就是告诉 React,我们希望这是一个受控的组件。不过,这只有在我们传递给它一个定义好的值时才会起作用!通过将 email 初始化为一个空字符串,确保该值永远不会被设置为 undefined

8、行内样式缺少括号

JSX 语法直观上与 HTML 很相似,但两者之间还是有一些不一样的地方。例如,如果你使用了 class 而不是 className。还有就是样式,在 HTML 中,style 被写成一个字符串:

<button style="color: red; font-size: 1.25rem">
  Hello World
</button>
复制代码

然而,在 JSX 中,我们需要将其指定为一个对象,并使用 camelCased (驼峰)属性名称。比如,你通常会这么做:

import React from 'react';

function App() {
  return (
    <button
      style={ color: 'red', fontSize: '1.25rem' }
    >
      Hello World
    </button>
  );
}

export default App;
复制代码

然后那就会发现控制台报错了:

7c9785498c3ed3cf8b7b847f8e7b4b73.jpeg
image.png

正确的做法是:

<button
  // 用 "{{", 而不是 "{":
  style={{ color: 'red', fontSize: '1.25rem' }}
>
  Hello World
</button>
复制代码

为什么要这样做?在 JSX 中,我们可以把任何有效的JS表达式放在这个标签里。比如说:

<button className={isPrimary ? 'btn primary' : 'btn'}>
复制代码

无论我们在 {} 里面放了什么,都会被认为是 JavaScript,结果将被设置为这个属性。 className 要么是 'btn primary',要么是 'btn'

如果我们把它分得更细一点,对象拉出来放到一个变量中会更清楚:

// 1. 创建一个样式属性对象
const btnStyles = { color: 'red', fontSize: '1.25rem' };

// 2. 把样式对象放到标签属性中
<button style={btnStyles}>
  Hello World
</button>

// 或者,一步到位
<button style={{ color: 'red', fontSize: '1.25rem' }}>
复制代码

9、useEffect 中的异步方法

假设我们在 useEffect 中请求 API,从中获取一些服务端数据,通常需要将请求方法写成异步的,比如这样:

import React from 'react';
import { API } from './constants';

function UserProfile({ userId }) {
  const [user, setUser] = React.useState(null);
  
  React.useEffect(() => {
    const url = `${API}/get-profile?id=${userId}`;
    const res = await fetch(url);
    const json = await res.json();
    
    setUser(json.user);
  }, [userId]);
  
  if (!user) {
    return 'Loading…';
  }
  
  return (
    <section>
      <dl>
        <dt>Name</dt>
        <dd>{user.name}</dd>
        <dt>Email</dt>
        <dd>{user.email}</dd>
      </dl>
    </section>
  );
}

export default UserProfile;
复制代码

然后你就会发现控制台这样了:

42e96bac7c14f8e1b47eceda6c695906.jpeg
image.png

你肯定想,不就缺少个 async 关键字吗:

React.useEffect(async () => {
  const url = `${API}/get-profile?id=${userId}`;
  const res = await fetch(url);
  const json = await res.json();
  setUser(json);
}, [userId]);
复制代码

不幸的是,这仍然不起作用;你将会得到一个新的错误信息:

destroy is not a function
复制代码

正确的做法应该是:

React.useEffect(() => {
  async function runEffect() {
    const url = `${API}/get-profile?id=${userId}`;
    const res = await fetch(url);
    const json = await res.json();
    setUser(json);
  }
  
  runEffect();
}, [userId]);
复制代码

10、未及时取消事件绑定

当使用 useEffect() 来管理副作用时,一定要记得自己手动清理一下。如果不这样做,会导致内存泄漏和其他问题。

useEffect(() => {
  window.addEventListener('resize', handleResize);
}, []);
复制代码

正确的做法是:

useEffect(() => {
  window.addEventListener('resize', handleResize);
  return () => {
      window.removeEventListener('resize', handleResize);
  }
}, []);
复制代码

结语

我是考拉🐨,一个热心的前端菜鸟程序员。如果你上进,喜欢前端,想学习前端,那咱们可以交朋友,一起摸鱼哈哈,摸鱼群,关注我,拉你进群,有5000多名前端小伙伴在等着一起学习哦 -->

 
 

Node 社群

我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。

b50a61a35a670607585c4e5d5f404a08.png

“分享、点赞、在看” 支持一波👍
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: VueReact 都是目前非常流行的前端框架,它们的演变都是源于对于前端开发的不断探索和优化。Vue 最初是由华人开发者尤雨溪在 2014 年推出的,它的设计理念是简单易用、高效灵活。而 React 则是由 Facebook 开发的,它的特点是组件化开发、虚拟 DOM 和函数式编程思想。随着前端技术的不断发展,VueReact 都在不断地更新迭代,提供更加丰富的功能和更好的性能,成为了前端开发中不可或缺的工具之一。 ### 回答2: VueReact是当前流行的前端框架,它们都有着丰富的功能和强大的社区支持。下面将简要介绍它们的演变过程。 Vue的演变可以追溯到2014年,由中国开发者尤雨溪创建。最初的版本是Vue.js 0.6,它是一个比较简单的框架,主要用于解决数据绑定和组件化的问题。随着版本的更新,Vue不断增加了更多的特性,例如指令系统、组件复用、状态管理等。在2016年发布的Vue 2.0中,重点加强了性能和渲染速度,并引入了虚拟DOM。 React的演变可以追溯到2013年,由Facebook团队开发。最初的版本是React 0.3.0,它主要关注UI组件的构建。随着版本的更新,React引入了虚拟DOM的概念,通过比较虚拟DOM树的差异来提高渲染性能。React还引入了JSX语法,使得编写具有复杂逻辑的组件更加直观。 在演变过程中,VueReact都趋向了更加成熟和强大。现在的VueReact都提供了丰富的生态系统,并且拥有庞大的社区支持和活跃的开发者Vue在中国开发者中更加流行,而React在全球范围内有更广泛的应用。两者都具备优秀的性能和灵活的组件化架构,可根据项目需求来选择使用。 总结来说,VueReact都经历了演变过程,不断增加特性和改进性能,成为了现今流行的前端框架,为开发者提供了更好的开发和用户体验。无论是选择Vue还是React,都能够获得良好的开发体验和项目效果。 ### 回答3: VueReact都是目前非常流行的前端框架,它们的演变可以追溯到不同的起点。 Vue的演变可以追溯到2013年,当时Evan You在工作中面临着AngularJS的一些问题,如过于复杂的API和性能问题。于是他决定自己写一个简洁且易于学习的框架,这就是Vue的起源。Vue最初的版本是基于AngularJS的思想,但添加了一些自己的创新,如指令和组件的概念。随着时间的推移,Vue团队不断完善和改进框架,添加了响应式数据绑定和虚拟DOM等重要特性。目前,Vue已经发展成为一个功能强大,易于使用且社区活跃的框架。 React的演变可以追溯到2011年,当时Facebook的工程师Jordan Walke为了解决用户界面构建的困扰,创造性地将JavaScript和HTML结合,开发了一个名为React的库。React的初衷是为了提供高性能的用户界面,通过虚拟DOM的技术减少DOM操作,从而提高渲染性能。随着React在Facebook内部的成功应用,Facebook于2013年将其开源,之后也得到了广泛的应用和发展。React通过引入组件化的开发模式,使得构建大型应用变得更加简单和可维护。 VueReact目前都有很庞大的社区支持,有很多优秀的第三方库和插件可以扩展它们的功能。两者的发展都聚焦于提高开发效率和性能,且相互借鉴对方的一些创意和特性。Vue在概念上更加接近传统的MVC模式,而React更加注重构建组件化的应用。随着前端技术的不断发展,VueReact也在不断演进和完善自己的功能,以满足开发者对于更好用户体验的追求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值