跳转至

Building Components

  1. npm i bootstrap@5.2.3

  2. main.tsx: import './index.css' => import "bootstrap/dist/css/bootstrap.css";

  3. mkdir components under src folder.

  4. Create ListGroup.tsx in components folder.

  5. Import this component into App.tsx and remove Message component because it is unnecessary.

  6. Copy the html code of list group from bootstrap and paste it into ListGroup.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function ListGroup() {
  return (
    <ul className="list-group">
      <li className="list-group-item">An item</li>
      <li className="list-group-item">A second item</li>
      <li className="list-group-item">A third item</li>
      <li className="list-group-item">A fourth item</li>
      <li className="list-group-item">And a fifth one</li>
    </ul>
  );
}
  • class is reserved keyword in Javascript or Typescript. Rename all of them into className in JSX.
  • Wrap the JSX markup in parenthesis. This is necessary to break the markup into multiple lines.

Fragments#

Return multiple elements in React is not allowed. Wrap the entire expression inside the div or anthoer element. Select all the code and open the command palette, enter wrap, press the enter and specify the type of element that we want to use to wrap the entire code. The better way is to use empty angle brackets to tell React to use a fragment to wrap all this children.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import { Fragment } from "react";
function ListGroup() {
  return (
    <>
      <h1>List</h1>
      <ul className="list-group">
        <li className="list-group-item">An item</li>
        <li className="list-group-item">A second item</li>
        <li className="list-group-item">A third item</li>
        <li className="list-group-item">A fourth item</li>
        <li className="list-group-item">And a fifth one</li>
      </ul>
    </>
  );
}

Render lists#

Render a list of items dynamically.

  • Each child in a list should have a unique "key" prop that uniquely identifies that item. React need this to keep track of our items to know what part of the page should be updated.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
function ListGroup() {
  const items = ["New York", "San Francisco", "Tokyo", "London", "Paris"];

  return (
    <>
      <h1>List</h1>
      <ul className="list-group">
        {items.map((item, index) => (
          <li key={index} className="list-group-item">
            {item}
          </li>
        ))}
      </ul>
    </>
  );
}
  • Retrieve items from an API. Quite often each item has a prop like id.

Conditional Rendering#

1
2
3
{items.length === 0 ? <p>No item found.</p> : null}

{items.length === 0 && <p>No item found.</p> }

Handling Events#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{items.map((item, index) => (

<li
  key={index}
  className="list-group-item"
  onClick={(e) => console.log(e)}
>
  {item}
</li>
))}

SyntheticBaseEvent: Build in class in React, a wrapper around the native brower event object. Because different browsers have different implementations of event objects.

Type Annotation#

1
2
3
4
import { MouseEvent } from "react";
const handleClick = (event: MouseEvent) => {
event;
};
1
2
3
4
5
6
7
8
<ul className="list-group">
  {items.map((item, index) => (
 <li key={index} className="list-group-item" onClick={handleClick}>
   {item}

 </li>
  ))}
</ul>

Not calling the function, like handleClick(),we just pass a reference.

Managing State (Mutable)#

This is how we tell React that our component can have state that will change over time.
Each component is going to have its own state.

1
2
3
4
5
6
7
8
import { useState } from "react";
// Hook
const arr = useState(-1)
arr[0] // variable
arr[1] // updater function

// Destructure this array
const [variable, setVariable] = useState(default)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import { useState } from "react";
const [selectedIndex, setSelectedIndex] = useState(-1);
...
<ul className="list-group">
  {items.map((item, index) => (
 <li
   key={index}
   className={
  selectedIndex === index
    ? "list-group-item active"
    : "list-group-item"
   }
   onClick={() => setSelectedIndex(index)}
    >
   {item}
 </li>
  ))}
</ul>

Passing Data via Props (Immutable)#

Make component reuseable.
个人理解:组件就是函数(模板),props 就是形参

1
2
3
4
5
6
// Type annotation of object: interface
interface Props {
  items: string[];
  heading: string;
}
function ListGroup({ items, heading }: Props) {...}
1
2
3
4
// Desrtucture the object
function ListGroup(props: Props) {...}
=>
function ListGroup({ items, heading }: Props) {...}

ListGroup.jsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import { useState } from "react";
// type annotation of object: interface
interface Props {
  items: string[];
  heading: string;
}

function ListGroup({ items, heading }: Props) {
  const [selectedIndex, setSelectedIndex] = useState(-1);
  return (
    <>
      <h1>{heading}</h1>
      {items.length === 0 ? (
        <p>No item found.</p>
      ) : (
        <ul className="list-group">
          {items.map((item, index) => (
            <li
              key={index}
              className={
                selectedIndex === index
                  ? "list-group-item active"
                  : "list-group-item"
              }
              onClick={() => setSelectedIndex(index)}
            >
              {item}
            </li>
          ))}
        </ul>
      )}
    </>
  );
} 

export default ListGroup;

App.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import ListGroup from "./components/ListGroup";
function App() {
  const items = ["New York", "San Francisco", "Tokyo", "London", "Paris"];
  return (
    <div>
      <ListGroup items={items} heading="Cities" />
    </div>
  );
}
export default App;

Passing Functions via Props (Immutable)#

ListGroup.jsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// type annotation of object: interface
interface Props {
  items: string[];
  heading: string;
  onSelectItem: (item: string) => void;
}
function ListGroup({ items, heading, onSelectItem }: Props) {
  ...
  onClick={() => {
   setSelectedIndex(index);
   onSelectItem(item);
    }}
  }
  ...

App.jsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import ListGroup from "./components/ListGroup";

function App() {
  const items = ["New York", "San Francisco", "Tokyo", "London", "Paris"];
  const handleSelectItem = (item: string) => console.log(item);
 
  return (
    <div>
      <ListGroup
        items={items}
        heading="Cities"
        onSelectItem={handleSelectItem}
      />
    </div>
  );
}

export default App;

Passing Children#

在 React 中,children 是一个特殊的 props,它用于在组件的标签之间传递内容。类型标注里要写明 children 的类型,这里 children 是一个 ReactNode,表示可以传递任何有效的 React 内容,比如字符串、元素、甚至是其他组件。

Alert.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { ReactNode } from "react";
interface Props {
  type:
    | "primary"
    | "secondary"
    | "success"
    | "danger"
    | "warning"
    | "info"
    | "light"
    | "dark";
  children: ReactNode;
}

const Alert = ({ type, children }: Props) => {
  return (
    <div className={`alert alert-${type}`} role="alert">
      {children}
    </div>
  );
};

export default Alert;

App.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import Alert from "./components/Alert";

function App() {
  return (
    <div>
      <Alert type="warning">
        Hello <span>World</span>!
      </Alert>;
    </div>
  );
}

export default App;

Exercise - Button#

当一个 props 是可选时,在类型标注时,在 props 名称后面加上 ? 表示可有可无。适合 props 已经设置默认值的情况。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface Props {
  type?:
    | "primary"
    | "secondary"
    | "success"
    | "danger"
    | "warning"
    | "info"
    | "light"
    | "dark"
    | "link";
  children: React.ReactNode;
  onClick: () => void;
}

const Button = ({ type = "primary", onClick, children }: Props) => {
  return (
    <button type="button" className={`btn btn-${type}`} onClick={onClick}>
      {children}
    </button>
  );
};

export default Button;

Exercise - Alert#

目标:点击刚刚创建的 Button,弹出 Alert,点击 ❎ 可以关闭 Alert

已知 Alert 的样式:

1
2
3
4
5
<div class="alert alert-warning alert-dismissible fade show" role="alert">
  <strong>Holy guacamole!</strong> You should check in on some of those fields below.
  // ❎ 按钮
  <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>

需要一个变量来控制 Alert 组件的显隐,因此在 App.tsx 中使用 UseState Hook 去定义一个布尔值变量和 set 函数。在 jsx 中利用逻辑与实现显隐控制。

Alert.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { ReactNode } from "react";

interface Props {
  type:
    | "primary"
    | "secondary"
    | "success"
    | "danger"
    | "warning"
    | "info"
    | "light"
    | "dark";
  children: ReactNode;
  onClose: () => void;
}

const Alert = ({ type, children, onClose }: Props) => {
  return (
    <div
      className={`alert alert-${type} alert-dismissible fade show`}
      role="alert"
    >
      {children}
      <button
        type="button"
        className="btn-close"
        data-bs-dismiss="alert"
        aria-label="Close"
        onClick={onClose}
      ></button>
    </div>
  );
};

export default Alert;

App.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Alert from "./components/Alert";
import Button from "./components/Button";
import { useState } from "react";

function App() {
  const [isShow, setIsShow] = useState(false);
  return (
    <div>
      {isShow && (
        // 点击将 isShow 设置为 false,即不显示 Alert
        <Alert type="warning" onClose={() => setIsShow(false)}>
          Hello World!
        </Alert>
      )}
      // 点击将 isShow 设置为 True,即显示 Alert
      <Button onClick={() => setIsShow(true)}>
        Hello <strong>World</strong>!
      </Button>
    </div>
  );
}

export default App;

2024-06-07 23:40 2024-07-09 01:04

评论