React
React
React
EXTREMELY POPULAR, DECLARATIVE, COMPONENT-BASED, STATE-DRIVEN JAVASCRIPT LIBRARY FOR BUILDING USER INTERFACES CREATED BY FACEBOOK
非常流行的、声明式的、基于组件的、状态驱动的 javascript 库,用于构建用户界面,由 facebook 创建
Part 1
Why use React?
对比原生 JS 和框架,同步数据渲染更简便。
单向数据流
只能通过动作修改状态,从而触发视图更新无法反向控制,子组件只能监听父组件传来的道具修改自己的状态,无法直接修改父组件的数据。 优点:
- 所有的状态改变可记录,可追踪,源头可追溯
- 数据操作更直观更容易理解
- 可维护性
容易实现性能优化,通过数据流动的方向可以精准控制组件的渲染时机,避免不必要的渲染和更新。
虚拟DOM
本质:维持一个对象构成的虚拟DOM树,每次变动后通过 React 的 diff 算法(fiber)调和计算出差值触发网页渲染。 优点:
- 跟原生 DOM 操作比提高效率,每次更新只需要局部更新,不需要每次改动都触发重绘和回流
- 用空间换时间,在牺牲了内存开销和初次渲染的性能的前提下,提高了渲染效率
- 增加了可维护性,代码简约,开发者不用考虑实际DOM操作
JSX
JSX是一种 JavaScript 的语法扩展,常用于描述 React 组件的 UI结构。JSX 看起来像是 HTML,但实际上是 JavaScript 的一种语法糖,它允许在 JavaScript 中直接书写类似 HTML 的标签,并最终通过babel编译。最终转化为虚拟DOM树渲染React 认为渲染逻辑本质上与其他 UI逻辑内在耦合,比如,在 UI 中需要绑定处理事件、在某些时刻状态发生变化时需要通知到 Ul,以及需要在 UI中展示准备好的数据。 优点:
- JSX语法和HTML基本没差别,非常直观和益读,便于维护,而且使用声明式编程,降低开发成本
- JSX内嵌JS表达式,这使得UI渲染的逻辑非常灵活,条件渲染,列表渲染可以直接用三元表达式和.map实现(与Vue的v-if等需要学习的非原生JS语法糖相比,React可以直接使用js语法实现条件渲染)
React Hook
React Hook是 React 16.8引入的一种新特性,它允许在函数组件中使用状态和其他 React 特性而不需要编写类组件。Hook 提供了一组函数使得在函数组件中能够实现以前只能在类组件中使用的功能,比如管理状态、生命周期方法、上下文等。 避免了类组件写起来复杂,生命周期繁多还强制super,this管理困难的问题,同时也符合函数式编程的思想。 请各位朋友以后直接编写函数式组件取代类组件
优点:
- 简化代码结构
- 提高代码复用性
- 更好的逻辑组织
- 避免类组件的缺陷
- 轻量级,易测试
- 未来可能只更新React Hook的新特性
缺点:
- 需要重新学习适应
- 复杂度的提升(useEffect)
- 性能陷阱
- 无法完全模拟React生命周期
Create a React app
Uses slow and outdated technologies (i.e. webpack).
Jonas 提到 webpack 构建的 create-react-app 虽然很全能,但是过时了(主要因为 webpack 构建太慢,现在开发的项目就是 webpack,热加载要 1-2 分钟,折磨)。他会大部分采用 webpack,最后改为 Vite。
Review JS skill for React
介绍了 Destructuring 解构赋值,一些数组和对象的操作,在JS 笔记里已经有很详细的介绍了。
Jonas 介绍的插件很好用,quokka,能实时查看单 js 文件的变量输出,直接 inline 显示。
Pizza Menu
never nest functions in App()
Props
父组件给子组件传值, One way, Read-only。Vue 中也是这样
Angular has a 2-way data flow.
State
update State will trigger React re-render.
import { useState } from 'react';
useState()
会返回一个数组。
Controlled Component
在 React 中,onChange
事件处理器通常用于处理用户输入并更新组件的状态。在你给出的代码中,onChange
被绑定到一个函数,该函数将调用setDiscription
函数来更新description
状态。
当用户在输入框中输入时,每次键盘按键都会触发onChange
事件。事件处理器会获取输入框的当前值(e.target.value
),并使用setDiscription
函数将其设置为新的description
状态。
这种模式被称为受控组件,因为 React 控制了输入元素的状态。这使得你可以直接从组件的状态中获取输入元素的值,而不需要从 DOM 中查询它。这也使得你可以在用户输入时实时更新或验证数据。
Thinking in ReactMind
Where to place State?
最后一条很重要,State Lifting,如果两个兄弟组件都用到了同一个 State,这个 State 应该放到这两个兄弟组件的父组件中,否则会导致 rerender 非常卡,例子就是上节课的滑动 input,如果只在某一个兄弟组件中定义,在另一个兄弟组件中使用。拖动 input 时会导致其他组件 rerender,导致每次拖动一格就 rerender,不能流畅拖来拖去。
Children props
不使用自闭合标签,将内容写在开合标签中间,然后在 props 里用 children 关键字接收,可以直接填写 jsx。
Part 2 Components
How to split up a UI into components?
- Component Size
- Logical Seperation
- Reusablility
- Responsibility/complexity
- Personal Coding Style
So try not to create new components too early
Props Drilling
组件嵌套深了之后,经过 lifting 的 state 放在 app,再传下去就要传很多遍。
解决方法: Component composition
就是 children props 传 component
PropsType
用来检验 props type,但现在都使用 typescript 来进行这种工作。
Part 3 How React works?
Components, Instance, Element
How React renders?
React renders 并不是传统意义上的在浏览器上渲染,而是在 react 内部,这个 Render Phase 中渲染,然后进入 Commit Phase,将修改提交到 DOM。
1. Render is triggered
只有在初始化和 state 更新时才会触发 render,为整个应用程序触发 render,且不是立即触发,而是等 js 有空闲,但通常都是几毫秒延迟。setState 有排队。
2. Render Phase
2.1 Virtual DOM
虚拟 Dom,react 文档里已经不再提到这个词,但 jonas 还是在这里使用,因为大家都在用。
React 渲染更改了的 Component,但在 virtualDom 里会重新渲染所有组件,这是必须的因为 React 不知道是否有子组件会被影响。这样做消耗不大因为创建虚拟 DOM is fast and cheap,但每次更新郑哥真实 DOM 是 inefficient and wasteful。
2.2 Reconciliation
在 React 中,Reconciliation(协调)是指 React 在更新组件时进行的一种算法,用于比较新旧虚拟 DOM 树,并确定如何高效地将变更应用到实际的 DOM 上。
当组件的状态或属性发生变化时,React 将重新渲染组件,并生成新的虚拟 DOM 树。Reconciliation 算法会比较新旧虚拟 DOM 树的差异,然后决定如何更新实际的 DOM 结构以反映这些变化,而无需重新渲染整个页面。
Reconciliation 算法的主要目标是尽可能高效地更新 DOM,以避免不必要的操作,提高性能,并尽量减少用户感知到的页面更新延迟。
Reconciliation 算法的工作过程包括:
- 对比新旧虚拟 DOM 树:React 使用一种称为“diffing”的技术来比较新旧虚拟 DOM 树的差异。它会逐层遍历两棵树的节点,并找出节点之间的差异。
- 找出差异:在对比过程中,React 将找出哪些节点需要更新、删除或添加到 DOM 中。这些差异通常包括节点的类型、属性、文本内容等方面的变化。
- 应用变更:一旦找到了差异,React 将会根据这些差异来更新实际的 DOM 结构,从而反映组件状态或属性的变化。
React 的 Reconciliation 算法是非常高效的,它会尽量减少 DOM 操作的次数,并且优先处理最重要的更新任务,以保证页面的响应性和流畅性。
2.3 Fiber
Fiber 树(Fiber Tree)是 React 中用于管理组件生命周期、调度更新和执行渲染的数据结构。它是 React Fiber 架构的核心部分之一。
在 React 16 版本之前,React 使用了一种称为“Stack reconciler”的算法来管理组件的更新和渲染。在这个算法中,React 会使用递归调用来遍历整个组件树,执行组件的生命周期方法和生成虚拟 DOM,这样的方式存在一些问题,比如无法中断和恢复渲染,可能导致页面卡顿。
为了解决这些问题,React 团队引入了 Fiber 架构。Fiber 架构使用了一种名为“Fiber”的数据结构来表示组件树,并且引入了一种新的调度算法,使得 React 可以灵活地中断、暂停和恢复渲染过程,从而实现更好的用户体验和更高的性能。
Fiber 树的主要特点包括:
可中断性:Fiber 架构允许 React 在渲染过程中中断当前任务,执行其他优先级更高的任务,然后在恢复时继续之前的工作。这使得 React 能够更好地响应用户输入和实现更流畅的页面更新。
渐进式渲染:Fiber 架构支持渐进式渲染,即将渲染工作分成多个步骤,根据任务的优先级逐步执行,从而避免了长时间的页面阻塞。
调度器:Fiber 架构引入了一个名为调度器(Scheduler)的模块,负责根据任务的优先级和当前系统资源情况来调度任务的执行顺序。
总的来说,Fiber 树是 React 中用于实现可中断、渐进式渲染的一种数据结构,它为 React 应用带来了更好的性能和用户体验。
2.4 Reconciliation in action
现在有一个 Modal,showModal 为 false 之后,React 渲染 New virtual DOM,Fiber 根据 Fiber Tree 和 New virtual DOM 来判断出哪些 DOM 是需要更新,删除的。在 New virtual DOM 中,所有的 children component 都会被重新渲染,但 Fiber 在检查过后发现 Video 不需要重新渲染。
Render Phase 会生成 List of effects 并交给下一阶段。
3. Commit Phase and Bowser Paint
React 不负责 操作 DOM,是另一个库 ReactDOM 来操作,这样就可以通过更换不同的"Renderer"来进行各种平台的开发。
4. Recap
Diffing
Element 不变,只有 props 变的时候,此 element 和子 element,和 state 都不会改变。
示例中,由于切换每个 Tab 都只是给同一个叫 activeTab 的组件传递不同的 content,所以 tab 中的 likes state 不会改变,导致我们切换 tab 的时候,每个 tab 的 like 不会刷新。
Key prop
- 告诉 diffing 算法元素是唯一的,允许 react 区分同一个组件的多个实例
- 当一个 key 在 render 之间保持不变,元素将会被保留在 DOM 中(即使他在树中的位置改变了)
- 当一个 key 在 render 之间改变了,元素将会被删除重建(即使在树中的位置一样)
用法:
在 list 中使用
使用 key 去重置 state
Render logic and Event Handler Functions
React 组件应该像纯函数一样,对于相同的输入(props 和 state),总是产生相同的输出。这意味着render
方法不应该有任何副作用,比如发起网络请求或者修改外部状态。这样可以确保组件的行为是可预测和一致的。
State Updates Auto Batched
不会每次 state 改变就 re-render 一次
Update State in asynchronous
在设置完 state 之后立刻 log 出来,会发现 state 还没改变。这是因为 state 在 re-render 之后才会改变。
React18+之后才能 auto batch everywhere,17 及之前的版本不能在 timeout 和 promises 时 auto batch,意味着会渲染多次。
Event
复习 Event 的传播
Event Propagation
事件传播
点击按钮以后,Event 会被创造,但不是在 button 处,而是在 DOM 树的顶端,然后沿着整个树传播,最后到达目标元素,在这里可以用 Event handler function 处理。Event 到达目标元素后会立刻返回顶端,这是 Bubbling phase。在冒泡途中,如果父组件有相同事件,也会被依次触发(两个 div 嵌套,点击时会触发两个 onClick)。
事件的冒泡阶段(bubbling phase)是指事件在 DOM 树中向上传播的阶段。当一个元素上触发了某个事件(比如点击事件),该事件会首先在触发元素上被捕获(capture)到,然后沿着 DOM 树向上传播,直到根节点为止。
在冒泡阶段,事件会从触发元素的最深层的子元素开始向上传播,逐级触发父元素的相同事件,直到达到事件流的最外层,即 DOM 树的根节点。这意味着如果你在某个元素上监听了某个事件,当这个元素上触发了该事件时,该事件会向上传播,依次触发该元素的所有父级元素的相同事件。
冒泡阶段的事件流顺序与 DOM 树的层级结构相反,因为事件是从最内层的元素向外传播的。这种事件传播机制使得我们可以在父元素上捕获子元素的事件,或者在共同的祖先元素上处理多个子元素的事件。
在事件冒泡阶段,我们可以通过调用 event.stopPropagation()
方法来阻止事件的进一步传播,或者通过 event.stopImmediatePropagation()
方法来阻止事件的传播并阻止该事件在当前元素上的其他事件监听器的执行。
Event Delegation 事件委托
避免冒泡影响性能
- 在统一父元素上添加 Event Handler
- 用 e.target 获取触发事件的具体元素
- 如果是 button 触发的事件,在父元素的 Event Handler 里处理事件
Event in React
React 会把所有的 handler 都绑在#root 上。
React 包装了原生 JS 事件,加了防止冒泡。
React VS. Vue
相比于 Vue 这种框架,React 只是一个库,没有封装好的 http 请求,路由和样式等等,你可以自由选择这些第三方库,但更新和保持同步是一个问题。
React Ecosystem
React Framework
Summary
Part 4 Side Effect
Lifecycle
Where to create side effect?
副作用基本上是“React 组件和组件外部世界之间的交互”。我们也可以把一个副作用想象成“实际做了一些事情的代码”。举例说明:数据获取、设置订阅、设置计时器、手动访问 DOM 等等。
useEffect()
export default function App() {
const [movies, setMovies] = useState([]);
const [watched, setWatched] = useState([]);
fetch(`http://www.omdbapi.com/?apikey=${KEY}&s=interstellar`)
.then(res => res.json())
.then(data => setMovies(data.Search));
return <>...</>;
}
如果直接这样请求并更新 state,每次更新 state 都会触发 re-render,重新渲染后又会触发 fetch,从而再次触发更新 state,进入死循环,无限发送 fetch 请求。
所以需要用 useEffect
export default function App() {
const [movies, setMovies] = useState([]);
const [watched, setWatched] = useState([]);
useEffect(function () {
fetch(`http://www.omdbapi.com/?apikey=${KEY}&s=interstellar`)
.then(res => res.json())
.then(data => setMovies(data.Search));
}, []);
return <>...</>;
}
useEffect
是 React 中的一个 hook,它允许你在函数组件中执行副作用操作,例如数据获取、订阅、定时器设置和清理等。useEffect
是 React 16.8 版本引入的,它提供了一种替代类组件中 componentDidMount
、componentDidUpdate
和 componentWillUnmount
生命周期方法的方法。
useEffect
接受两个参数:
函数:这是一个副作用函数,它会在组件渲染到屏幕上后执行。这个函数可以执行任何需要的副作用操作,比如发起网络请求、更新 DOM 元素、设置事件监听器等。此外,这个函数也可以返回一个清理函数,当组件卸载或者需要停止订阅时,React 会自动调用这个清理函数。
依赖数组:这是一个可选参数,它是一个数组,包含了副作用函数依赖的值。只有当依赖数组中的值发生变化时,副作用函数才会重新执行。如果依赖数组为空(
[]
),那么副作用函数只会在组件挂载时执行一次;如果依赖数组不存在,则每次渲染后都会执行副作用函数。
以下是 useEffect
的一些常见用途:
数据获取
在组件挂载或更新后,你可能需要从服务器获取数据。
useEffect(() => {
fetch('/api/data')
.then(response => response.json())
.then(data => setData(data))
.catch(error => console.error(error));
}, [setData]); // 假设 setData 是一个更新组件状态的函数
更新 DOM
你可能需要直接操作 DOM 元素,例如添加或删除样式。
useEffect(() => {
const element = document.getElementById('myElement');
if (element) {
element.focus();
}
}, []); // 空依赖数组意味着这个效应只会在组件挂载时执行一次
事件监听
为浏览器事件添加监听器,并在组件卸载时移除监听器。
useEffect(() => {
const handleResize = () => {
setWindowSize(window.innerWidth, window.innerHeight);
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, [setWindowSize]); // 当 setWindowSize 改变时,重新添加或移除监听器
定时器
设置和清除定时器。
useEffect(() => {
const timer = setTimeout(() => {
console.log('定时器触发');
}, 1000);
return () => {
clearTimeout(timer);
};
}, []); // 空依赖数组
表单输入
处理表单输入变化。
useEffect(() => {
const handleInputChange = event => {
setInputValue(event.target.value);
};
const inputElement = document.getElementById('myInput');
inputElement.addEventListener('input', handleInputChange);
return () => {
inputElement.removeEventListener('input', handleInputChange);
};
}, [setInputValue]); // 当 setInputValue 改变时,重新添加或移除事件监听器
useEffect
的灵活性和强大功能使得它成为函数组件中处理副作用的关键工具。通过将副作用逻辑封装在 useEffect
钩子中,你可以更容易地编写可维护、可预测的 React 组件。
Handler VS. useEffect
优先使用 Event Handler,没讲为什么
useEffect Dependency Array
useEffect 中使用到的 state 或 prop 都要写进 dependency array,也就是 useEffect 的第二个参数,否则会出现 stale closure 这个 bug。
useEffect 在浏览器渲染完成之后加载
console.log 总是先打印,因为 useEffect 总是在浏览器渲染完成之后才执行,绑定所有执行和绑定 query 谁写在前面谁先执行。
CleanUp function
cleanup 函数在下一次 effect 执行之前执行,和在组件 unmount 之后运行,但由于闭包,还能访问到变量。
解决 racing problem
Part 5 React Hooks
What are Hooks?
Rules
只能在 top level 调用,且不要半路就 return,总之不要改变 Hooks 的顺序
只能在 React function 内调用
用条件查询会破坏 Hooks 链表(顺序)。这种方式实现了不需要手动编号。
useState summary
useState summary
useRef
在 React 中,useRef
是一个钩子函数,它可以用来创建一个可变的引用对象。这个对象的 .current
属性可以被改变,而不会导致组件重新渲染。
useRef
的主要用途有两个:
- 引用 DOM 元素:你可以使用
useRef
创建一个引用对象,然后将这个对象赋值给 JSX 元素的ref
属性。这样,你就可以在组件内部访问和操作这个 DOM 元素。例如,你可以获取输入框的值,或者改变元素的样式。
const inputRef = useRef();
// 在某个函数中
let inputValue = inputRef.current.value;
// 在 JSX 中
<input ref={inputRef} />
- 存储可变的值:
useRef
创建的引用对象的.current
属性可以存储任何值,并且你可以在不触发组件重新渲染的情况下改变这个值。这对于存储定时器 ID、当前的 Promise、当前的状态、不需要展示给用户等非状态值非常有用。
const timerID = useRef();
// 在某个函数中
timerID.current = setTimeout(() => {
console.**log**('Hello, world!');
}, 1000);
请注意,每次组件重新渲染时,useRef
返回的引用对象都是同一个。这意味着你可以在多次渲染之间保持和共享数据。
state vs ref
不能在 render logic 里直接改变 ref,需要在 useEffect 里改。
Custom Hooks
自定义 hooks 可以在 npm 安装别人的
Part 6 Classy component
旧版 React 形式
相关信息
施工中
Part 7 useReducer
useReducer
是 React 的一个 Hook,它接受一个 reducer 函数和一个初始状态作为参数,然后返回一个新的状态和一个能够更新该状态的 dispatch 函数。
在 JavaScript 中,reducer 是一个纯函数,它接受当前的状态和一个动作,然后返回一个新的状态。在 useReducer
中,你可以将复杂的组件状态逻辑提取到 reducer 函数中,使其更易于测试和理解。
这是一个基本的 useReducer
的使用示例:
import React, { useReducer } from 'react';
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
在这个例子中,我们创建了一个简单的计数器。我们定义了一个 reducer 函数,它接受当前的状态和一个动作,然后根据动作的类型来增加或减少计数。我们使用 useReducer
Hook 来管理状态,并使用 dispatch 函数来发送动作。
Why useReducer?
useState VS. useReducer
Part 8 React Router, SPA, CSS Module
Vite
npm create vite@4 // 使用vite创建项目
npm i eslint vite-plugin-eslint eslint-config-react-app --save-dev //配置通过vite创建的项目的eslint
Style in React
CSS Modules
React Routing
<BrowserRouter>
<Routes>
<Route index element={<HomePage />} />
<Route path="product" element={<Product />}></Route>
<Route path="pricing" element={<Pricing />}></Route>
<Route path="app" element={<AppLayout />}>
{/* 嵌套路由 用<Outlet /> 接收固定渲染位置 */}
<Route index element={<CityList />} />
<Route
path="cities"
element={<CityList cities={cities} loading={loading} />}
/>
<Route
path="countries"
element={<CountryList loading={loading} cities={cities} />}
/>
<Route path="form" element={<p>form</p>} />
</Route>
<Route path="login" element={<Login />}></Route>
<Route path="*" element={<PageNotFound />}></Route>
</Routes>
</BrowserRouter>
URL 传参
Part 9 Context API
Solution of prop drilling
用组件重组{children}解决 prop drilling 会有不能拆分的时候:例如,当我们需要在一个组件中维护一些内部状态,并且这个状态需要被多个子组件共享时,我们可能就无法将这个组件拆分。
所以需要直接从 app 组件传到对应的子组件,这时候可以用到 Context API。
Provider
import { createContext, useContext, useState } from 'react';
import { faker } from '@faker-js/faker';
const PostContext = createContext();
function createRandomPost() {
return {
title: `${faker.hacker.adjective()} ${faker.hacker.noun()}`,
body: faker.hacker.phrase(),
};
}
function PostProvider({ children }) {
const [posts, setPosts] = useState(() =>
Array.from({ length: 30 }, () => createRandomPost())
);
const [searchQuery, setSearchQuery] = useState('');
// Derived state. These are the posts that will actually be displayed
const searchedPosts =
searchQuery.length > 0
? posts.filter(post =>
`${post.title} ${post.body}`
.toLowerCase()
.includes(searchQuery.toLowerCase())
)
: posts;
function handleAddPost(post) {
setPosts(posts => [post, ...posts]);
}
function handleClearPosts() {
setPosts([]);
}
return (
<PostContext.Provider
value={{
posts: searchedPosts,
onClearPosts: handleClearPosts,
onAddPost: handleAddPost,
searchQuery,
setSearchQuery,
}}>
{children}
</PostContext.Provider>
);
}
function usePost() {
const context = useContext(PostContext);
if (context === undefined) {
throw new Error('usePost must be used within a PostProvider');
}
return context;
}
export { usePost, PostProvider };
2 States types and replacement



Part 10 Performance Optimization and Advanced useEffect
Optimize waste re-renders
When re-render?
Changing Props 不会带来 re-renders,只是父组件传的值变化后,父组件 re-render 引发的子组件 re-render。
A render 并不意味着 DOM 实际上被更新了,它只是意味着组件函数被调用了(还要经过 diff 来判断是否更新 DOM)。但这可能是一个昂贵的操作。
Wasted render: A render that didn't produce any change in the DOM
Diff 中并未发生改变,但还是要 diff 一遍,虽然 React 很快,但在更新频繁或者组件 slow 时需要解决。
Slow Children Component
如果父组件中包含一个渲染很慢的子组件,每次父组件渲染时,子组件都会渲染导致整体速度变慢,此时,如果子组件只是一个独立组件,不依赖父组件的 props,此刻可以 refactor,将子组件作为 children prop 或者其他 prop,传入父组件。这时候 React 会渲染 SlowComponent 并传入父组件,然后就不会再重新渲染。
<Counter>
<SlowComponent />
</Counter>
// 或者
<Counter slowComponent={<SlowComponent />}>
</Counter>
利用浏览器开发者插件来看火焰图。
Memoization
Memo
Object 和 Function 作为 prop 传入,每次都不一样。这时候需要 useMemo 来记住 value,useCallback 来记住 function。
useMemo useCallback
setter 函数 不用传递进 dependency array 里 因为它默认不会变。
useMemo缓存values,useCallback缓存函数
Bundle Size
lazy loading
useEffect Dependency Array Rules
When NOT to use Effect?
Closure in React hooks
Essentially the effect function can not see all of these variables here, unless we specify them in the dependency array.
Part 11 Redux
reducer里不能写sideeffect,sideEffect要写在middleware(thunk)里。
Context API VS. Redux

When to use context API or Redux?
Part 12 Real-world Apps
Routing - React Router
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Home from './ui/Home';
import Menu from './features/menu/Menu';
import Cart from './features/cart/Cart';
import CreateOrder from './features/order/CreateOrder';
import Order from './features/order/Order';
import AppLayout from './ui/AppLayout';
const router = createBrowserRouter([
{
// 根Layout,由固定部分和<Outlet /> (动态切换页面内容)组成
element: <AppLayout />,
children: [
{ path: '/', element: <Home /> },
{ path: '/menu', element: <Menu /> },
{ path: '/cart', element: <Cart /> },
{ path: '/order/new', element: <CreateOrder /> },
{ path: '/order/:orderId', element: <Order /> },
],
},
]);
function App() {
return <RouterProvider router={router}></RouterProvider>;
}
export default App;