Learning React #2 | 青训营笔记

185 阅读3分钟

Notes for Learning React #2

🚨 It is written only for myself (It may have no reference value)

🔗 Source code in my GitHub

Rendering Lists

Simple List

  1. Put books object in an array
const books = [
  {
    id: 0,
    img: 'https://images-na.ssl-images-amazon.com/images/I/81bGKUa1e0L._AC_UL900_SR300,450_.jpg',
    title: 'Atomic Habits',
    author: 'James Clear',
  },
  {
    id: 1,
    img: 'https://images-na.ssl-images-amazon.com/images/I/71IJiOOyb1L._AC_UL900_SR300,450_.jpg',
    title: 'Outlive',
    author: 'Peter Attia MD',
  },
];
  1. Map books members into a new array o JSX nodes, and return it as newNames. (Remember: Array.prototype.map() is going to return a new array)
const names = ['John', 'Lucas', 'Harry'];
const newNames = names.map(name => <h1>{name}</h1>);

Here is the results:

import React from 'react';
import * as ReactDomClient from 'react-dom/client';

import './index.css';

const books = [
  {
    id: 0,
    img: 'https://images-na.ssl-images-amazon.com/images/I/81bGKUa1e0L._AC_UL900_SR300,450_.jpg',
    title: 'Atomic Habits',
    author: 'James Clear',
  },
  {
    id: 1,
    img: 'https://images-na.ssl-images-amazon.com/images/I/71IJiOOyb1L._AC_UL900_SR300,450_.jpg',
    title: 'Outlive',
    author: 'Peter Attia MD',
  },
];

const names = ['John', 'Lucas', 'Harry'];
const newNames = names.map(name => <h1>{name}</h1>);

function BookList() {
  return <section className="booklist">{newNames}</section>;
}

const Book = props => {
  const { img, title, author } = props;
  return (
    <article className="book">
      <img className="image" src={img} alt={title} />
      <h1>{title}</h1>
      <h4>{author}</h4>
    </article>
  );
};

const root = ReactDomClient.createRoot(document.getElementById('root'));
root.render(<BookList />);

Proper List

Nothing new. It is just finished our demo by using map(). (Yeah, that's what I think).

import React from 'react';
import * as ReactDomClient from 'react-dom/client';

import './index.css';

const books = [
  {
    id: 1,
    img: 'https://images-na.ssl-images-amazon.com/images/I/81bGKUa1e0L._AC_UL900_SR300,450_.jpg',
    title: 'Atomic Habits',
    author: 'James Clear',
  },f
  {
    id: 2,
    img: 'https://images-na.ssl-images-amazon.com/images/I/71IJiOOyb1L._AC_UL900_SR300,450_.jpg',
    title: 'Outlive',
    author: 'Peter Attia MD',
  },
  {
    id: 3,
    img: 'https://images-na.ssl-images-amazon.com/images/I/71aG+xDKSYL._AC_UL900_SR300,450_.jpg',
    title: 'The 48 Laws of Power',
    author: 'Robert Greene',
  },
];

function BookList() {
  return (
    <section className="booklist">
      {/* It forwarding a `book` object to parent component(Book) */}
      {books.map(book => (
        <Book book={book}></Book>
      ))}
    </section>
  );
}

// Every time `Book` component accepts a `book` object from `BookList` component
// So we can see 3 results are returned in the log of browser
const Book = props => {
  console.log(props);

  // `book` is an object of `props`
  // We are not destructing the `props`
  // Actually, we are destructing the 'book'
  const { img, title, author } = props.book;
  return (
    <article className="book">
      <img className="image" src={img} alt={title} />
      <h1>{title}</h1>
      <h4>{author}</h4>
    </article>
  );
};

const root = ReactDomClient.createRoot(document.getElementById('root'));
root.render(<BookList />);

Key Prop and Spread Operator

Key Prop

⚠️ JSX elements directly inside a map() call always need keys!

We should always include the key in our data

function BookList() {
  return (
    <section className="booklist">
      {/* It forwarding a `book` object to parent component(Book) */}
      {books.map(book => (
        <Book key={book.id} book={book}></Book>
      ))}
    </section>
  );
}

And we can also add index as parameter in map()

function BookList() {
  return (
    <section className="booklist">
      {/* It forwarding a `book` object to parent component(Book) */}
      {books.map((book, index) => (
        <Book key={index} book={book}></Book>
      ))}
    </section>
  );
}

Spread Operator

Sometimes, passing props gets very repetitive:

function BookList() {
  return (
    <section className="booklist">
      {/* It forwarding a `book` object to parent component(Book) */}
      {books.map((book, index) => (
        <Book key={index} img={img} title={title} author={author}></Book>
      ))}
    </section>
  );
}

We can also use ES6 feature Spread Operator (which is more concise):

function BookList() {
  return (
    <section className="booklist">
      {/* It forwarding a `book` object to parent component(Book) */}
      {books.map((book, index) => (
        <Book key={index} {...book}></Book>
      ))}
    </section>
  );
}

❓ OK, so here is the question: What's the difference between the parameter {...book} and book={book} in <Book />

  • I think {book} is an object that is assigned to the book variable(object) in book={book} expression.
  • So the book here is an object that contains data in another object.
  • The {...book} will forward the entry instead.
  • So it forwards the data object itself, not the object of object.

So we will refactor the code like this:

function BookList() {
  return (
    <section className="booklist">
      {books.map(book => (
        <Book key={book.id} {...book}></Book>
      ))}
    </section>
  );
}

const Book = ({ img, title, author }) => {
  return (
    <article className="book">
      <img className="image" src={img} alt={title} />
      <h1>{title}</h1>
      <h4>{author}</h4>
    </article>
  );
};

Event Basics

This is the results:

const Book = ({ img, title, author }) => {
  const handleClick = () => {
    alert('Hello World!');
  };

  return (
    <article className="book">
      <img className="image" src={img} alt={title} />
      <h1>{title}</h1>
      <h4>{author}</h4>
      <button type="button" onClick={handleClick}>
        reference example
      </button>
    </article>
  );
};

📝 Note

We defined handleClick function and passed it as a prop to <button>.

Event handler function:

  • Are usually defined inside our components(Book here).
  • Have names that start with handle, followed by the name of the event.

By convention, it is common to name event handlers as handle followed by the event name. We’ll often see onClick={handleClick}, onMouseEnter={handleMouseEnter}, and so on.

We have reference event and we will pass a parameter into an event handler:

const Book = ({ img, title, author }) => {
  const clickHandler = () => {
    alert('Hello World!');
  };
  const complexExample = author => {
    console.log(author);
  };

  return (
    <article className="book">
      <img className="image" src={img} alt={title} />
      {/* We can also make event handler as inline way */}
      <h1 onClick={() => console.log(title)}>{title}</h1>
      <h4>{author}</h4>
      {/* We have reference `clickHandler` here ⬇️ */}
      <button type="button" onClick={clickHandler}>
        reference example
      </button>
      {/* It log in the console when rendering the page without any click event. */}
      {/* Why was that happening? */}
      <button type="button" onClick={complexExample(author)}>
        complex example
      </button>
    </article>
  );
};

⚠️ Pitfall:

🔴 REMEMBER: Functions passed to event handlers must be passed, not called.

  • onclick={handleClick} (correct) ✅
  • onclick={handleClick(param)} (false) ❌

In the first example, the handleClick function is passed as an onClick event handler. This tells React to remember it and only call your function when the user clicks the button.

In the second example, the () at the end of handleClick() fires the function immediately during rendering, without any clicks. This is because JavaScript inside the JSX { and } executes right away.

If we want to pass a parameter, we can use inline code:

  • onclick={() => handleClick(param)} (correct: return handleClick()) ✅
  • onclick={handleClick(param)} (false: fire handleClick()) ❌

I think () => handleClick(param) in onClick is a bit hard to understand