Files
rikako-note/react/react文档.md
2023-05-30 00:27:04 +08:00

553 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 快速入门
## 创建和嵌套组件
React应用由组件组成每个组件都是UI的一部分组件拥有自己的外观和逻辑。
### 组件是返回标签的js函数
```js
function MyButton() {
return (
<button>I'm a button</button>
);
}
```
上述已经声明了MyButton组件可以将该组件嵌套到另一个组件中
```js
export default function MyApp() {
return (
<div>
<h1>Welcome to my app</h1>
<MyButton />
</div>
);
}
```
> react组件必须以大写字母开头而html组件则是必须以小写字母开头可以通过组件开头字母的大小写来区分html组件和react组件
## jsx编写标签
上述返回标签的语法被称为jsx大多数react项目都支持jsx。
jsx比html更加严格:
1. 所有的标签都要有闭合标签(例如`<br/>`)
2. 组件不能返回多个标签,只能返回一个标签,如果存在多个,必须将其包含到一个公共的父级`<div>``<>`
```js
function AboutPage() {
return (
<>
<h1>About</h1>
<p>Hello there.<br />How do you do?</p>
</>
);
}
```
## 添加样式
React中可以在标签中添加`className`属性来添加样式其和html中的`class`工作方式相同
```html
<img className="avatar" />
```
```css
/* In your CSS */
.avatar {
border-radius: 50%;
}
```
## 显示数据
在jsx中标签位于js中而可以在标签内通过`{}`来计算js表达式并将其填充到标签中
```js
return (
<h1>
{user.name}
</h1>
);
```
jsx还可以将表达式的值传递给标签属性通过`{}`传递表达式的值
```js
return (
<img
className="avatar"
src={user.imageUrl}
/>
);
```
同时可以通过js表达式来复制css
```js
const user = {
name: 'Hedy Lamarr',
imageUrl: 'https://i.imgur.com/yXOvdOSs.jpg',
imageSize: 90,
};
export default function Profile() {
return (
<>
<h1>{user.name}</h1>
<img
className="avatar"
src={user.imageUrl}
alt={'Photo of ' + user.name}
style={{
width: user.imageSize,
height: user.imageSize
}}
/>
</>
);
}
```
## 条件渲染
在React中没有特殊语法编写条件故而可以在js中通过if条件引入jsx
```js
let content;
if (isLoggedIn) {
content = <AdminPanel />;
} else {
content = <LoginForm />;
}
return (
<div>
{content}
</div>
);
```
也可以使用如下方式:
```js
<div>
{isLoggedIn ? (
<AdminPanel />
) : (
<LoginForm />
)}
</div>
```
## 渲染列表
可以通过如下方式将js数组渲染为列表
```js
const products = [
{ title: 'Cabbage', id: 1 },
{ title: 'Garlic', id: 2 },
{ title: 'Apple', id: 3 },
];
const listItems = products.map(product =>
<li key={product.id}>
{product.title}
</li>
);
return (
<ul>{listItems}</ul>
);
```
上述示例中,`<li>`有一个key属性对于列表中的每个属性都应该传递给其一个字符串或数字的key用于在兄弟节点之间唯一标识该元素。
## 响应事件
可以在组件中声明事件处理函数来响应事件
```js
function MyButton() {
function handleClick() {
alert('You clicked me!');
}
return (
<button onClick={handleClick}>
Click me
</button>
);
}
```
## 更新界面
如果希望组件维护状态信息可以通过导入useState来完成
```js
import { useState } from 'react';
function MyButton() {
const [count, setCount] = useState(0);
```
其中count记录当前的状态而setCount则是用于改变状态的函数可以为数组中变量取任何名称。
```js
import { useState } from 'react';
export default function MyApp() {
return (
<div>
<h1>Counters that update separately</h1>
<MyButton />
<MyButton />
</div>
);
}
function MyButton() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<button onClick={handleClick}>
Clicked {count} times
</button>
);
}
```
## 使用hook
以`use`开头的函数被称为hookuseState是react提供的一个内置hook。
hook比普通函数更为严格只能在组件顶层或其他hook的顶层调用hook。如果想要在条件或循环中使用hook请新建一个组件并在组件内部使用。
## 组件之间共享数据
如果想要将状态在多个组件之间共享,需要将状态提升存储到最近的公共父组件中
```js
export default function MyApp() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<div>
<h1>Counters that update together</h1>
<MyButton count={count} onClick={handleClick} />
<MyButton count={count} onClick={handleClick} />
</div>
);
}
function MyButton({ count, onClick }) {
return (
<button onClick={onClick}>
Clicked {count} times
</button>
);
}
```
此时由MyApp传递给MyButton的值称之为prop
## jsx展开传递props
如果父组件想要将接收到的props全部传递给子组件无需在子组件上列出props中全部属性可以使用`...props`来进行展开
```js
function Profile(props) {
return (
<div className="card">
<Avatar {...props} />
</div>
);
}
```
## 将jsx作为子组件传递
当采用如下形式时:
```js
<Card>
<Avatar />
</Card>
```
其中父组件接收到的prop中children将代表接受到的子组件内容
```js
import Avatar from './Avatar.js';
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
export default function Profile() {
return (
<Card>
<Avatar
size={100}
person={{
name: 'Katsuko Saruhashi',
imageId: 'YfeOqp2'
}}
/>
</Card>
);
}
```
此时想要嵌套在父组件内部的子组件可以通过props.children来访问。
## 条件渲染
如果在某些条件下组件不想显示任何内容可以返回null
```js
function Item({ name, isPacked }) {
if (isPacked) {
return null;
}
return <li className="item">{name}</li>;
}
```
## 组件的渲染和提交
### 组件渲染的原因
- 组件的初次渲染
- 组件(或先祖组件)的状态发生了变化
#### 初次渲染
当引用启动时,会触发初次渲染
#### 状态更新时重新渲染
当初次渲染完成后可以通过set函数来更新state并触发渲染。更新组件状态会将重新渲染加入到队列。
对于初次渲染React会调用根部组件的方法来进行渲染而对于状态更新触发的渲染react只会调用状态更新对应组件的函数
> 对于组件的渲染其过程是递归的。如果待渲染的组件中包含了子组件那么react会对子组件进行渲染对子组件中包含的孙组件也同样。渲染会对组件子树中所有的组件进行渲染。
#### 渲染差异
React只会在渲染之间存在差异时才会更改React节点如一个节点如果渲染之间从父组件接受到了不同的props此时该组件才会发生重新渲染。
## state快照
在渲染函数中state的快照是不可变的即使在调用setState函数将更新state后旧组件中获取的state值仍然没有变化。
```js
// 即使调用setTitle此快照中的title值在setTitle之前和之后仍然没有变化
// 而新渲染的组件title初始值则为改变之后的值
function Title() {
const [title,setTitle]=useState('Morning News');
return (
<div>
<h1>{title}</h1>
<button onClick={
()=>{
alert(title);
setTitle(title==='Morning News'?'Evening Newng News':'Morning News');
alert(title);
}
}>Switch</button>
</div>
)
}
```
## 向setState中传入更新函数
react支持向setState中传入更新函数而不是更新后的值例如
```js
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);
```
此时react会将上述三个函数以此加入到队列中并在下次渲染时遍历执行队列中的函数。
## 更新state对象
如果向useState中传入object那么在调用setState函数时则是应该传入一个新的对象而不是在原有state对象上进行更新。
**应该将state对象看作是不可变的调用setState时应该创建一个state对象的副本并且修改副本对象后用副本对象的值去更新setState方法。**
```js
// 如果想要修改对象中的某个字段,可以使用如下方法
let oldObj = {
name : 'kazusa',
isMale: false,
};
let newObj = {
// 展开对象中的属性
...odlObj,
// 新设置某个属性,用于覆盖旧的值
name:'mashiro',
}
```
### 修改嵌套对象中的属性
```js
setPerson({
...person, // Copy other fields
artwork: { // but replace the artwork
...person.artwork, // with the same one
city: 'New Delhi' // but in New Delhi!
}
});
```
## Immer
如果要修改深层嵌套的state值可以引入Immer库来完成对象的拷贝工作。通过Immer库可以无需关心对象不可变而是直接以可变的语法来修改对象属性而Immer会实际帮忙处理对象不可便的工作。
```js
// 通过该import代替react中useState
import { useImmer } from 'use-immer';
// 然后可以直接修改对象中的值
const [person, updatePerson] = useImmer({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
}
});
function handleNameChange(e) {
updatePerson(draft => {
draft.name = e.target.value;
});
}
```
## 更新state数组
和object一样在react中数组应该也要是不可变的在更新state中的值时也应该再创建一个新的数组并用新的数组值设置
## 重新渲染时组件状态的保存
只要一个组件还在**UI树中相同的位置**那么react就会保存其state如果组件被移除或另有一个类型的组件被渲染在相同的位置那么react将会丢弃旧组件的state。
如果不同同类型组件被渲染在相同位置state仍然会被保存。
### 在UI树结构的同一位置渲染不同组件
如果在两次渲染之间在UI树的同一位置渲染了相同类型的组件那么默认情况下两次ui组件的state相同的。
如果要显式指定两次渲染的组件是不同的各自有独立的state可以为两次渲染的组件指定不同的key
```js
import { useState } from 'react';
export default function Scoreboard() {
const [isPlayerA, setIsPlayerA] = useState(true);
return (
<div>
{isPlayerA ? (
<Counter person="Taylor" />
) : (
<Counter person="Sarah" />
)}
<button onClick={() => {
setIsPlayerA(!isPlayerA);
}}>
下一位玩家!
</button>
</div>
);
}
function Counter({ person }) {
const [score, setScore] = useState(0);
const [hover, setHover] = useState(false);
let className = 'counter';
if (hover) {
className += ' hover';
}
return (
<div
className={className}
onPointerEnter={() => setHover(true)}
onPointerLeave={() => setHover(false)}
>
<h1>{person} 的分数:{score}</h1>
<button onClick={() => setScore(score + 1)}>
加一
</button>
</div>
);
}
```
> 注意key并不需要全局唯一key只用于指定父组件内部的顺序
### 前后两次组件渲染位于不同UI树位置
如果前后两次组件渲染时位于不同的ui树位置如果要保持前后两次渲染组件state相同可以为前后两次渲染指定相同的key那么在前后两次渲染时即使ui树位置不同state仍然会延续相同。
state和树中位置相关例如
```js
// 第一次渲染
return (
<ul>
<li key={id1}><Item key="a" /></li>
<li key={id2}><Item key="b"/></li>
</ul>
)
// 第二次渲染
return (
<ul>
<li key={id2}><Item key="b"/></li>
<li key={id1}><Item key="a"/></li>
</ul>
)
```
在上述两次渲染中,`item a`和`item b`父节点`<li>`的顺序虽然发生了改变,但是能够通过`<li>`元素的key属性来区分故而`item a`和`item b`的state仍然会保持原样。显示效果为列表第一行元素和第二行元素发生了对调。
## Reducer
reducer是一个函数接受两个参数
- 当前state
- action代表改变状态的参动作数据结构可以自定义
reducer方法的返回值是一个新的statereact会将旧的状态设置为新的状态。
### Reducer使用
```js
import { useReducer } from 'react';
// 用于替换useState
// useReducer接受两个参数一个reducer函数一个state的初始值
// 其会返回有状态的值和一个dispatch函数dispatch函数用于分发action给reducer
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
// 之后可以向dispatch函数提交action
function handleAddTask(text) {
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}
function handleChangeTask(task) {
dispatch({
type: 'changed',
task: task,
});
}
// reducer函数会处理action
function tasksReducer(tasks, action) {
switch (action.type) {
case 'added': {
return [
...tasks,
{
id: action.id,
text: action.text,
done: false,
},
];
}
case 'changed': {
return tasks.map((t) => {
if (t.id === action.task.id) {
return action.task;
} else {
return t;
}
});
}
case 'deleted': {
return tasks.filter((t) => t.id !== action.id);
}
default: {
throw Error('未知 action: ' + action.type);
}
}
}
```
## Context
如果需要在先祖节点和子孙节点间隔多级时从先祖节点向子孙节点传递数据可以使用Context.
Context使用如下
### 创建Context
```js
import { createContext } from 'react';
export const LevelContext = createContext(1);
```
### 子孙组件从Context中获取值
```js
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
export default function Heading({ children }) {
const level = useContext(LevelContext);
// ...
}
```
### 先祖节点提供Context
```js
import { LevelContext } from './LevelContext.js';
export default function Section({ level, children }) {
return (
<section className="section">
<LevelContext.Provider value={level}>
{children}
</LevelContext.Provider>
</section>
);
}
```