10 mistakes Vue players often make when switching to React, how many mistakes have you made?

1. Use 0 for conditional rendering

This may be a problem that many novices have encountered! I also made this mistake in the past, but if you say it is a mistake, it can also be said to be  a pit: 0 is a false value, but it cannot be used for conditional rendering.react 

Consider the following example:

Maybe you take it for granted that he will  items display the component when the array is empty  ShoppingList . But it actually shows a 0! This is because 0  JavaScript is a false value in , && the operator short-circuits, and the entire expression is parsed as 0. It is equivalent to: 

function App() {
  return (
    <div>
      {0}
    </div>
  );
}

Unlike the other false values ​​( '', null, false etc.), the number 0  JSX is a valid value in . After all, in many cases, we do want to print the number 0!

The correct approach is:

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. Mutation status

Take a look at this common page first:

code:

 

handleAddItem The function will be called whenever a new item is added . However, it doesn't work! When we enter an item and submit the form, the item is not being added to the shopping list.

The problem is that we are violating perhaps  React the most core principle of the world - immutable state. React relies on the address of a state variable to determine if the state has changed. When we push an item into an array, we don't change the address of the array, so  React there's no way to tell that the value has changed.

The correct approach is:

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

It is not recommended to modify an existing array, but to create a new array from scratch. The difference here is between editing an existing array and creating a new one.

Similarly, for data of object type:

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

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

Why reactmutation state is not recommended

  • DEBUG: If you use console.log and don't change state, your past logs won't be corrupted by recent state changes, and you can clearly see state changes between renders

  • Optimization: A common optimization strategy is to skip rendering if the previous propsand statenext state are the same. reactIf you never change the state, checking for changes can be very blocky. If so prevProps === props, react can be sure that it didn't happen internally. Variety

  • New features: React is building new features that rely on treating the state as a snapshot, which will break new features if you're updating past state versions

  • Requirement change: Some values ​​that require undo/redo and display history are easier to do without mutation, this is because you can keep past values ​​in copies and redo them where applicable

  • Simpler implementation: since reactit doesn't rely on mutation, it doesn't need to do anything with your object, it doesn't need to hijack your object. Always wrap them into a proxy, or do other work on initialization like many "reactive" solutions do. This is also what  react allows you to put any object in state (no matter how large) without additional performance or correctness pitfalls.

3. Unique key

Surely you will often see a warning in the console:

Warning: Each child in a list should have a unique "key" prop.

for example:

The console will report a warning:

Whenever we render an array of elements, we need to provide React with some additional context so that it can identify each item, usually a unique identifier.

You can do this:

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

But this is not a recommended practice. This approach works sometimes, but in some cases it causes some pretty big problems. As you gain a better understanding of how React works, you'll be able to tell if it's okay on a case-by-case basis.

There is a perfectly safe way to solve this problem:

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

    const nextItems = [...items, nextItem];
    setItems(nextItems);
}

crypto.randomUUID is a method built into the browser (it's not a third-party package). It is available in all major browsers. This method generates a unique string, such as: d9bb3c4c-0459-48b9-a94c-7ca3963f7bd0. By dynamically generating an ID when the user submits the form, we ensure that each item in the shopping list has a unique ID.

So, a better approach is:

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

You'd better not be clever, just for convenience, do this:

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

Spawning it in like this  JSX will cause it  key to change on every render. These elements are destroyed and recreated whenever  key there is a change , which can have a large negative impact on performance.React 

This schema, generated when the data is first created  key, can be applied in various situations. For example, here's how I create a unique ID when fetching data from the server:

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. Missing spaces

You must have encountered this situation:

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;

Note that there is no space in front: login in

This is because (the tools that convert the JSX we write into browser-friendly JavaScript) can't really distinguish between syntactic whitespace and whitespace we've added for indentation/code readability. JSX编译器

The correct approach is:

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

 Tips: If you use Prettier, it will automatically add these space characters for you!

5. Access state after changing state

This is probably the most common mistake made by newbies in 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;

 What if you have to take it? There is a way:

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

6. Return multiple elements

Sometimes, a component needs to return multiple top-level elements.

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

export default LabeledInput;

In HTML, htmlForthe attribute is <label>the ID for the form control associated with the element. It is used to specify <label>the relationship between elements and form controls, so as to mark and access form controls.

htmlForThe role of the attribute is to <label>associate the element with the form control. When the user clicks <label>the element, the corresponding form control will be triggered automatically. This provides a convenient way to <label>select or focus the associated form control by clicking on the element, rather than just clicking on the form control itself.

When using htmlForthe property, you need to set its value to the ID of the form control to be associated. Examples are as follows:

<label htmlFor="username">用户名:</label>
<input type="text" id="username" name="username" />

In the code above, htmlForthe attribute <label>associates the element with the text input with the ID "username". When the user clicks on <label>the element, the browser will automatically focus on the text input box, which improves the user experience.

Usage scenarios include but are not limited to:

  • Click on the text label to focus on the related input box or checkbox.
  • Select a radio button by clicking on the label.
  • Improves accessibility so that screen readers can properly markup associated form controls.

In summary, htmlForattributes are used to <label>associate elements with form controls, so that users can click on <label>elements to operate related form controls, improving interactivity and accessibility.

We want our  LabeledInput component to be able to return two elements: one <label>and one <input>. This happens because JSX is compiled into plain JavaScript like this: 

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

 In JavaScript we are not allowed to return multiple things like this. This is also the reason why this method is not feasible, just like:

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

The correct approach is:

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

7. Uncontrolled to controlled switching

Let's look at a typical form scenario, binding an input to a React state

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;

If you start typing, you'll notice a console warning:

how to solve this problem? We need to initialize our state to an empty string:

const [email, setEmail] = React.useState('');

When we set  value a property, equals tells us  Reactthat we want this to be a controlled component. However, this will only work if we pass it a defined value! email Ensure that the value is never set to  by  initializing it to an empty string undefined.

8. The inline style lacks parentheses

JSX The syntax is intuitively  HTML similar to , but there are some differences between the two. For example, if you used  class instead of  className. There is also the style, in  HTML , style which is written as a string:

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

However, in , we need to specify it as an object, and use  (camel case) property names. For example, you usually do this: JSX camelCased 

import React from 'react';

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

export default App;

Then you will find that the console reports an error:

The correct approach is:

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

 Why do you want to do this? In  JSX , we can put any valid JS expression inside this tag. For example:

<button className={isPrimary ? 'btn primary' : 'btn'}>

Whatever we  {} put in it will be considered  JavaScriptand the result will be set to this property. className Either it is  , or it is  .'btn primary''btn'

If we broke it down a bit more, it would be clearer to pull the object out into a variable:

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

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

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

9. Asynchronous methods in useEffect

Suppose we  useEffect request  in API, and get some server-side data from it, usually we need to write the request method as asynchronous, such as this:

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

function UserProfile({ userId }) {
  const [user, setUser] = React.useState(null);
  
  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;

Then you will find the console like this:

You must be thinking, isn’t there a  async keyword missing: 

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

Unfortunately, this still doesn't work; you'll get a new error message:

destroy is not a function

We all know that useEffect one of the characteristics of the hook function is the cleanup function, that is,  return the function. If you  useEffect return anything from the hook function, it must be a cleanup function, which will be run when the component unmounts. componentWillUnmount Equivalent to lifecycle methods in class components  . In JavaScript, however,  async...await the program is made to wait for the completion of the asynchronous task before proceeding. Async functions also always return a Promise; the return value is automatically wrapped in a Promise if the function has not already returned  Promise .

According to the above writing method, the arrow function directly points to the return value, which is equivalent to returning a promise function, and it is no longer a cleanup function.

The correct approach should be:

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. Failure to cancel event binding in time

When using  useEffect() to manage side effects, be sure to remember to clean up manually yourself. Failure to do so can lead to memory leaks and other problems.

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

The correct approach is:

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

Guess you like

Origin blog.csdn.net/huihui_999/article/details/131915945