Skip to content

React Tutorial

https://www.youtube.com/watch?v=SqcY0GlETPk

Vite

Vite (French word for "quick", pronounced /vit/, like "veet") is a new breed of frontend build tooling that significantly improves the frontend development experience.

File Structure

bash
├── public/                # 静态资源目录
   ├── favicon.ico        # 网站图标
   └── robots.txt         # 搜索引擎爬虫配置文件

├── src/                   # 源代码目录
   ├── assets/            # 资源文件目录
   ├── images/        # 图片资源
   └── styles/        # 样式文件

   ├── components/        # 可复用组件目录
   ├── Button/
   └── Header/

   ├── hooks/             # 自定义 React Hooks
   ├── layouts/           # 布局组件
   ├── pages/             # 页面组件
   ├── services/          # API 服务
   ├── store/             # 状态管理(如 Redux)
   ├── utils/             # 工具函数
   ├── App.tsx           # 应用根组件
   ├── main.tsx          # 应用入口文件
   └── vite-env.d.ts     # Vite 类型声明文件

├── .gitignore            # Git 忽略文件配置
├── index.html            # HTML 模板
├── package.json          # 项目依赖配置
├── tsconfig.json         # TypeScript 配置
├── vite.config.ts        # Vite 配置文件
└── README.md             # 项目说明文档
bash
- `tsconfig.json`: telling TypeScript compiler how to compile code to javascript.
- `.tsx`: `ts` for TypeScript, `x` for react component.
- Using PascalCasing for react component, html tag is always lowercase.

public/

  • 存放不需要经过打包的静态资源
  • 可以通过绝对路径直接访问

src/assets/

  • 存放需要经过打包处理的资源文件
  • 包括图片、样式等

src/components/

  • 存放可复用的 React 组件
  • 每个组件通常包含自己的样式和测试文件

src/hooks/

  • 存放自定义 React Hooks
  • 用于提取可复用的状态逻辑

src/layouts/

  • 存放页面布局组件
  • 如页面框架、导航栏等

src/pages/

  • 存放页面级组件
  • 通常与路由配置对应

src/services/

  • 存放 API 接口封装
  • 处理与后端的数据交互

src/store/

  • 状态管理相关文件
  • 如 Redux、Zustand 等的配置和状态定义

src/utils/

  • 存放工具函数
  • 常用的辅助方法

配置文件

  • vite.config.ts:Vite 的配置文件
  • tsconfig.json:TypeScript 配置
  • package.json:项目依赖和脚本配置
  • index.html:应用的 HTML 模板

入口文件

  • src/main.tsx:应用的入口文件
  • src/App.tsx:根组件

建议

  1. 根据项目规模和需求适当调整目录结构
  2. 保持目录结构清晰,便于维护
  3. 可以添加 types/ 目录存放类型定义
  4. 可以添加 constants/ 目录存放常量
  5. 大型项目可以考虑按功能模块划分目录

Dom Render

  • Virtual DOM is data structure and properties in javascript
  • When the state changed, it will compare with the prev virtual DOM.
  • Then update the actual DOM(This is done by react-dom package).

Demo

Create Vite with React demo project.

yaml
npm create vite@4.1.0
yaml
cd foo
yaml
npm i
yaml
npm run dev

Install Bootstrap

bash
npm i bootstrap@5.2.3

CSS

  • index.css: global css, used by index.js.
  • app.css: component css, used by App.js and child components.

StrictMode

  • It will send 2 requests after render to find potential problems
  • Static check.

Prettier

Install code format plugin Prettier

bash
cmd+shift+p => format document => configure default formatter

It can also format document by click right mouse.

Component

In function, it only can return one element.

tsx
function ListGroup() {
  return (
    // <h1>List</h1> error here
    // its something like: React.createElement('h1')
    <ul className="list-group">
      <li className="list-group-item">An item</li>
    </ul>
  );
}

The solution is to wrap multi elements by div tags.

json
cmd+shit+p => wrap with abbreviation => div

Fragment

Use div wrap will add extra useless tag, use Fragment instead.

tsx
function ListGroup() {
  return (
    <Fragment>
      <h1>List</h1>
        <ul className="list-group">
          <li className="list-group-item">An item</li>
        </ul>
    </Fragment>
  );
}

Equivalent to <>

tsx
function ListGroup() {
  return (
    <>
      <h1>List</h1>
        <ul className="list-group">
          <li className="list-group-item">An item</li>
        </ul>
    </>
  );
}

Key Prop

Every child in a list should have a unique key prop. React needs it to track each item.

tsx
function ListGroup() {
  const items = ["New York", "San Francisco", "Tokyo", "London", "Paris"];
  return (
      <div>
        <h1>List</h1>
        <ul className="list-group">
          {items.map((item) => (
            <li key={item}>{item}</li>
          ))}
          </ul>
      </div>
  );
}

Chrome

Install React Developer Tools

https://react.dev/learn/react-developer-tools

Conditional Rendering

When use && in expression, if first args is true, the whole exp value will be the second arg.

tsx
function ListGroup() {
  const items = [];
  return (
      <div>
        <h1>List</h1>
        {items.length === 0 && <p>No item found</p>}
        <ul className="list-group">
          {items.map((item) => (
            <li key={item}>{item}</li>
          ))}
          </ul>
      </div>
  );
}

Equivalent to

tsx
{items.length === 0 ? <p>No item found</p> : null}
tsx
function ListGroup() {
  const items = [];
  const message = items.length === 0 ? <p>No item found</p> : null
  return (
      <div>
        <h1>List</h1>
        {message}
        <ul className="list-group">
          {items.map((item) => (
            <li key={item}>{item}</li>
          ))}
          </ul>
      </div>
  );
}

Use Function

tsx
function ListGroup() {
  const items = [];
  const getMessage = () => {
    return items.length === 0 ? <p>No item found</p> : null;
  }
  return (
      <div>
        <h1>List</h1>
        {getMessage()}
        <ul className="list-group">
          {items.map((item) => (
            <li key={item}>{item}</li>
          ))}
          </ul>
      </div>
  );
}

Handle Events

OnClick Prop, index and event args is optional.

tsx
function ListGroup() {
  const items = ["New York", "San Francisco", "Tokyo", "London", "Paris"];
  return (
    <div>
      <h1>List</h1>
      {items.length === 0 && <p>No item found</p>}
      <ul className="list-group">
        {items.map((item, index) => (
          <li
            className="list-group-item"
            key={item}
            onClick={(event) => console.log(item, index, event)}
          >
            {item}
          </li>
        ))}
      </ul>
    </div>
  );
}

Define function handler.

tsx
import { MouseEvent } from "react";

function ListGroup() {
  const items = ["New York", "San Francisco", "Tokyo", "London", "Paris"];
  const handleClick = (event: MouseEvent) => {
    console.log(event);
  };
  return (
    <div>
      <h1>List</h1>
      {items.length === 0 && <p>No item found</p>}
      <ul className="list-group">
        {items.map((item) => (
          <li
            className="list-group-item"
            key={item}
            onClick={handleClick}
          >
            {item}
          </li>
        ))}
      </ul>
    </div>
  );
}
tsx
import { MouseEvent } from "react";

function ListGroup() {
  const items = ["New York", "San Francisco", "Tokyo", "London", "Paris"];
  const handleClick = (item: string, index: number, event: MouseEvent) => {
    console.log(item, index, event);
  };
  return (
    <div>
      <h1>List</h1>
      {items.length === 0 && <p>No item found</p>}
      <ul className="list-group">
        {items.map((item, index) => (
          <li
            className="list-group-item"
            key={item}
            onClick={(event) => handleClick(item, index, event)}
          >
            {item}
          </li>
        ))}
      </ul>
    </div>
  );
}

Managing State

tsx
import { useState } from "react";

function ListGroup() {
  const items = ["New York", "San Francisco", "Tokyo", "London", "Paris"];
  const [selectedIndex, setSelectedIndex] = useState(-1);
  return (
    <div>
      <h1>List</h1>
      {items.length === 0 && <p>No item found</p>}
      <ul className="list-group">
        {items.map((item, index) => (
          <li
            className={
              selectedIndex === index
              ? "list-group-item active"
              : "list-group-item"
            }
            key={item}
            onClick={() => {setSelectedIndex(index)}}
          >
            {item}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ListGroup;

Passing Data via Props

Using interface

tsx
import { useState } from "react";

interface Props {
  items: string[];
  heading: string;
}

function ListGroup({ items, heading }: Props) {
  const [selectedIndex, setSelectedIndex] = useState(-1);

  return (
    <div>
      <h1>{heading}</h1>
      {items.length === 0 && <p>No item found</p>}
      <ul className="list-group">
        {items.map((item, index) => (
          <li
            className={
              selectedIndex === index
              ? "list-group-item active"
              : "list-group-item"
            }
            key={item}
            onClick={() => {setSelectedIndex(index)}}
          >
            {item}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ListGroup;
tsx
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

tsx
import { useState } from "react";

interface Props {
  items: string[];
  heading: string;
  onSelectItem: (item: string) => void;
}

function ListGroup({ items, heading, onSelectItem }: Props) {
  const [selectedIndex, setSelectedIndex] = useState(-1);

  return (
    <div>
      <h1>{heading}</h1>
      {items.length === 0 && <p>No item found</p>}
      <ul className="list-group">
        {items.map((item, index) => (
          <li
            className={
              selectedIndex === index
              ? "list-group-item active"
              : "list-group-item"
            }
            key={item}
            onClick={() => {
              setSelectedIndex(index);
              onSelectItem(item);
            }}
          >
            {item}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ListGroup;
tsx
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;

State vs Props

Props

  • Input passed to a component.
  • Similar to function args.
  • Immutable: can not change inside function, this is call functional programming principles.

State:

  • Data managed by a component.
  • Similar to local variables.
  • Mutable

When they changed, Both will re-render component and update the Dom accordingly.

ES7 React

Cursor install plugin ES7 React. And input rafce.

tsx
reactArrowFunctionExportComponent

Passing Children

All components support children prop. children prop can be any type, such as:

  • string
  • ReactNode for html content.
tsx
interface Props {
  children: React.ReactNode;
}

function Alert({ children }: Props) {
  return <div className="alert alert-primary">{children}</div>;
}

export default Alert;
tsx
import Alert from './components/Alert';

function App() {
  return (
    <div>
      <Alert>
        Hello World <span>YEAH</span>
      </Alert>
    </div>
  );
}

export default App;