React
中文文档:https://react.docschina.org/ 官方文档:https://react.dev/
安装
npm install react react-dom
react
:包含 React 的核心功能,包括创建组件、管理组件的生命周期和处理状态等。它是你编写 React 组件所需的基础库。
react-dom
:提供了将 React 组件渲染到 DOM中的特定方法。
框架构建
Next.js:npx create-next-app@latest
create-react-app:npx create-react-app my-app
vite:npm create vite@latest my-app -- --template react
自动导入
plugins: [
react(),
AutoImport({
imports: [
"react",
"react-router-dom",
{ react: ["Suspense", "createContext"] }, // 额外手动导入
],
eslintrc: {
enabled: true,
filepath: "./eslintrc-auto-import.json",
},
dts: "auto-imports.d.ts",
}),
]
基础
porps
export default function Profile() {
return (
<Card>
<Avatar
size={100}
person=
/>
</Card>
);
}
条件渲染
使用 JavaScript 的 if
语句、&&
和 ? :
运算符来选择性地渲染 JSX;
渲染列表
filter()
筛选需要渲染的组件和使用 map()
把数组转换成组件数组。
属性
html标签与jsx组件
标签开头为小写时,会被 jsx编译为 html标签
标签开头为大写时,会被 jsx识别为组件
className:
CSS style内联样式:
事件
<button onClick={handleClick}>Button</button>
更新state
setMessage(e.target.value)
等价于 setmessage(() => e.target.value)
setState((index) => index + 100)
作为一个组件的记忆,state 不同于在你的函数返回之后就会消失的普通变量。state 实际上“活”在 React 本身中——就像被摆在一个架子上!——位于你的函数之外。当 React 调用你的组件时,它会为特定的那一次渲染提供一张 state 快照。你的组件会在其 JSX 中返回一张包含一整套新的 props 和事件处理函数的 UI 快照 ,其中所有的值都是 根据那一次渲染中 state 的值 被计算出来的!
触发渲染(状态更新添加队列) -> 渲染中(根据set赋值后计算快照) -> 提交(DOM)
更新对象
setPosition({ x: e.clientX, y: e.clientY });
const nextArtists = [...artists.slice(0, insertAt)];
setArtists(nextArtists);
import { useImmer } from 'use-immer';
const [person, updatePerson] = useImmer({})
updatePerson(draft => {
draft.artwork.city = e.target.value;
});
updateMyList(draft => {
const artwork = draft.find(a => a.id === id);
artwork.seen = nextSeen;
});
state的保留与重置
每次重新渲染,组件状态对的保留与重置规则:
组件树上组件被移除,销毁状态;
组件树上位置未变化的组件的 state 会被保留下来,组件树决定状态(可使用key以及多个组件实现重置);
组件树上位置变化的组件会使 state 重置(旧的销毁,新的产生)
react中key更新,也会触发重新渲染
Reducer
function tasksReducer(tasks, action) {
if (action.type === 'added') {
......
return [];
}
}
import { useReducer } from 'react';
import { useImmerReducer } from 'use-immer';
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
const [tasks, dispatch] = useImmerReducer(tasksReducer, initialTasks);
dispatch(
{
type: 'deleted',
id: taskId,
}
);
Context
创建 context
import { createContext } from 'react';
export const LevelContext = createContext(1);
使用 context
import { LevelContext } from './LevelContext.js';
export default function Heading({ children }) {
const level = useContext(LevelContext); // 访问上层最近
// ...
}
提供 context
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
export default function Section({ children }) {
const level = useContext(LevelContext); // 访问上层最近
return (
<section className="section">
<LevelContext.Provider value={level + 1}>
{children}
</LevelContext.Provider>
</section>
);
}
reducer + context
创建 context
import { createContext } from 'react';
export const TasksContext = createContext(null);
export const TasksDispatchContext = createContext(null);
state 和 dispatch 函数 放入 context
import { TasksContext, TasksDispatchContext } from './TasksContext.js';
export default function TaskApp() {
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
// ...
return (
<TasksContext.Provider value={tasks}>
<TasksDispatchContext.Provider value={dispatch}>
...
</TasksDispatchContext.Provider>
</TasksContext.Provider>
);
}
接收使用
export default function TaskList() {
const tasks = useContext(TasksContext);
// ...
export default function AddTask() {
const [text, setText] = useState('');
const dispatch = useContext(TasksDispatchContext);
// ...
return (
// ...
<button onClick={() => {
setText('');
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}}>Add</button>
// ...
迁移逻辑
import { createContext, useContext, useReducer } from 'react';
const TasksContext = createContext(null);
const TasksDispatchContext = createContext(null);
export function TasksProvider({ children }) {
const [tasks, dispatch] = useReducer(
tasksReducer,
initialTasks
);
return (
<TasksContext.Provider value={tasks}>
<TasksDispatchContext.Provider value={dispatch}>
{children}
</TasksDispatchContext.Provider>
</TasksContext.Provider>
);
}
export function useTasks() {
return useContext(TasksContext);
}
export function useTasksDispatch() {
return useContext(TasksDispatchContext);
}
ref
使用 ref使组件记住某些信息(常规变量是记不住的),且让这些信息不会触发新的渲染
存储变量值
let ref = useRef(0);
function handleClick() {
ref.current = ref.current + 1;
alert('你点击了 ' + ref.current + ' 次!');
}
引用DOM
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<MyInput ref={inputRef} />
<button onClick={handleClick}>
聚焦输入框
</button>
</>
);
}
访问组件的DOM
// 这让从上面接收的 inputRef 作为第二个参数 ref 传入组件
const MyInput = forwardRef((props, ref) => {
return <input {...props} ref={ref} />;
});
export default function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<MyInput ref={inputRef} />
<button onClick={handleClick}>
聚焦输入框
</button>
</>
);
}
effect
Effects 会在渲染后运行一些代码,实现副作用;
渲染逻辑代码 位于组件的顶层,最终返回你想在屏幕上看到的 JSX。渲染的代码必须是纯粹的——就像数学公式一样,它只应该“计算”结果,而不做其他任何事情。
事件处理程序是嵌套在组件内部的函数,而不仅仅是计算函数。事件处理程序可能会更新输入字段、提交 HTTP POST 请求以购买产品,或者将用户导航到另一个屏幕。事件处理程序包含由特定用户操作(例如按钮点击或键入)引起的“副作用”;
Effect 允许你指定由渲染本身,而不是特定事件引起的副作用
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// 每次渲染后都会执行此处的代码
});
return <div />;
}
import { useState, useRef, useEffect } from 'react';
function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
// 渲染后
useEffect(() => {
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
});
return <video ref={ref} src={src} loop playsInline />;
}
指定Effect依赖
useEffect(() => {
// 这里的代码会在每次渲染后执行
});
useEffect(() => {
// 这里的代码只会在组件挂载后执行
}, []);
useEffect(() => {
//这里的代码只会在每次渲染后,并且 a 或 b 的值与上次渲染不一致时执行
}, [a, b]);
注:Effect 读取的每一个响应式值都必须在其依赖项中声明
添加清理(cleanup)函数
重复挂载组件,可以确保在 React 中离开和返回页面时不会导致代码运行出现问题
为了解决这个问题,可以在 Effect 中返回一个 清理(cleanup) 函数。
useEffect(() => {
const connection = createConnection();
connection.connect();
return () => {
connection.disconnect(); // 指定卸载组件时处理
};
}, []);
处理开发环境中 Effect 执行两次
控制非 React 组件
useEffect(() => {
const dialog = dialogRef.current;
dialog.showModal();
return () => dialog.close();
}, []);
订阅事件
useEffect(() => {
function handleScroll(e) {
console.log(window.scrollX, window.scrollY);
}
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
触发动画
useEffect(() => {
const node = ref.current;
node.style.opacity = 1; // 触发动画
return () => {
node.style.opacity = 0; // 重置为初始值
};
}, []);
获取数据
Effect 将会获取数据,清理函数应该要么 中止该数据获取操作,要么忽略其结果:
useEffect(() => {
let ignore = false;
async function startFetching() {
const json = await fetchTodos(userId);
if (!ignore) {
setTodos(json);
}
}
startFetching();
return () => {
ignore = true;
};
}, [userId]);
memo
组件记忆化
React 组件应该始终具有 纯粹的渲染逻辑。这意味着如果其 props、state 和 context 没有改变,则必须返回相同的输出。通过使用 memo,你告诉 React 你的组件符合此要求,因此只要其 props 没有改变,React 就不需要重新渲染。即使使用 memo,如果它自己的 state 或正在使用的 context 发生更改,组件也会重新渲染。
import { memo, useState } from 'react';
// name值未发生更改时,父组件更新时跳过Greeting更新
const Greeting = memo(function Greeting({ name }) {
return <h3>Hello{name && ', '}{name}!</h3>;
});
注:只有当你的组件经常使用完全相同的 props 重新渲染时,并且其重新渲染逻辑是非常昂贵的,使用 memo
优化才有价值。
收缩props变化:
import { useMemo } from "react";
// 仅当id和name改变,才意味着person对象变化了
const person = useMemo(() => { id, name }, [id, name]);
return <Profile person={person} />;
const Profile = memo(function Profile({ person }) {
// ...
});
记忆缓存
import { useMemo, useState } from 'react';
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
// 仅当 todos 或 filter 发生变化,visibleTodos才需重新计算,否则不会重新执行 getFilteredTodos()
const visibleTodos = useMemo(() => getFilteredTodos(todos, filter), [todos, filter]);
// ...
}]);
callback
当props传递的值总是函数,那么 memo
包裹组件跳过重新渲染是不生效的;
因为外部传递的函数每次都是新的,此时使用 useCallback
有效解决函数重新计算:
function ProductPage({ productId, referrer, theme }) {
// 在多次渲染中缓存函数
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]); // 只要这些依赖没有改变,使用缓存函数
return (
<div className={theme}>
{/* ShippingForm 就会收到同样的 props 并且跳过重新渲染 */}
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
自定义Hook
import { useState, useEffect } from 'react';
export function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
function handleOnline() {
setIsOnline(true);
}
function handleOffline() {
setIsOnline(false);
}
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
传递响应值
export function useChatRoom({ serverUrl, roomId }) {
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
connection.on('message', (msg) => {
showNotification('New message: ' + msg);
});
return () => connection.disconnect();
}, [roomId, serverUrl]);
}
import { useEffect, useEffectEvent } from 'react';
// ...
// 传递值包含事件处理
export function useChatRoom({ serverUrl, roomId, onReceiveMessage }) {
// 防止每次组件重新渲染时聊天室就会重新连接
const onMessage = useEffectEvent(onReceiveMessage); // useEffectEvent
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
connection.on('message', (msg) => {
onMessage(msg);
});
return () => connection.disconnect();
}, [roomId, serverUrl]); // ✅ 声明所有依赖
}
React哲学
一、拆解UI为组件层级结构
FilterableProductTable
SearchBar
ProductTable
ProductCategoryRow
ProductRow
二、使用 React 构建一个静态版本
// 产品目录行
function ProductCategoryRow({ categoryName }) {
// .....
}
// 产品行
function ProductRow({ product }) {
// .....
}
// 表格
function ProductTable({ products }) {
// .....
}
// 搜索过滤栏
function SearchBar() {
// .....
}
// 可过滤的产品表格
function FilterableProductTable({ products }) {
// .....
}
// 产品数据
const PRODUCTS = [
{ category: "Fruits", price: "$1", stocked: true, name: "Apple" },
// .....
];
export default function App() {
return <FilterableProductTable products={PRODUCTS} />;
}
三、找出 UI 精简且完整的 state 表示
将 state 作为应用程序需要记住改变数据的最小集合。
组织 state 最重要的一条原则是保持它 DRY。计算出你应用程序需要的绝对精简 state 表示,按需计算其它一切。
- 随着时间推移 保持不变?如此,便不是 state。
- 通过 props 从父组件传递?如此,便不是 state。
- 是否可以基于已存在于组件中的 state 或者 props 进行计算?如此,它肯定不是state!
四、验证 state 应该被放置在哪里
filterText、inStockOnly、filterProducts
// 可过滤的产品表格
function FilterableProductTable({ products }) {
const [filterText, setFilterText] = useState("");
const [inStockOnly, setInStockOnly] = useState(false);
const filterProducts = products.filter((product) => {
if (product.name.indexOf(filterText) === -1) return false;
if (inStockOnly && product.stocked === inStockOnly) return false;
return true;
});
return (
<>
<SearchBar
filterText={filterText}
inStockOnly={inStockOnly}
></SearchBar>
<ProductTable products={filterProducts}></ProductTable>
</>
);
}
五、添加反向数据流
function FilterableProductTable({ products }) {
const [filterText, setFilterText] = useState("");
const [inStockOnly, setInStockOnly] = useState(false);
// ......
return (
<>
<SearchBar
filterText={filterText}
inStockOnly={inStockOnly}
onFilterTextChange={setFilterText}
onInStockOnlyChange={setInStockOnly}
></SearchBar>
<ProductTable products={filterProducts}></ProductTable>
</>
);
}
// 搜索过滤栏
function SearchBar({ filterText, inStockOnly, onFilterTextChange, onInStockOnlyChange }) {
return (
<form>
<div>
<input
type="text"
value={filterText}
placeholder="Search..."
onChange={(e) => onFilterTextChange(e.target.value)}
/>
</div>
<div>
<label>
<input
type="checkbox"
id="stockCheckbox"
value={inStockOnly}
onChange={(e) => onInStockOnlyChange(e.target.checked)}
/>
Only show products in stock
</label>
</div>
</form>
);
}