React =》 构建用户界面的JS库,用户界面是由按钮、文本和图像等小的单元内容构建。
React可以组合成可重用、可嵌套的组件。
组件案例
function Profile() {
return (
<img src='https://i.xxx.com/test.jpg' alt=''/>
)
}
export default function Gallery() {
return (
<section>
<h1>Amazing scientists</h1>
<Profile/>
<Profile/>
<Profile/>
</section>
)
}
利用props传递数据给子组件
React组件会使用props来进行组件之间的通讯,每个父组件可以通过为子组件提供props的方式来传递信息
props =》 可以传递:对象、数组、函数、JSX等
function Card({ children }) {
return (
<div className='card'>
{children}
</div>
)
}
function Avatar({person, size}) {
return (
<img
className='avatar'
src={getImageUrl(person)}
alt={person.name}
width={size}
height={size}/>
)
}
export default function Profile() {
return (
<Card>
<Avatar
size={100}
person={{
name: 'T',
imageId: '12asd',
}}/>
</Card>
)
}
条件渲染
根据不同的条件来显示不同的东西,在React中,可使用JS语法,eg: if、&&、?:操作符实现有条件的渲染JSX
function Item({name, isPacked}) {
return (
<li className='item'>
{name} {isPacked && '✔'}
</li>
)
}
export default function PackingList() {
return (
<section>
<h1> Sally Ride's Packing List</h1>
<ul>
<Item isPacked={true} name='Space suit'/>
<Item isPacked={true} name='Helmet with a golden leaf'/>
<Item isPacked={false} name='Photo of Tam'/>
</ul>
</section>
)
}
渲染列表
针对数据集合进行遍历,在React中使用filter()和map()实现数组的过滤和转换,将数据数组转换为组件数组;
对于数组的每个元素向,指定一个key;
import {people} from './data.js';
ipmort { getImageUrl } from './utils.js';
export default function List() {
const listItems = people.map(person =>
<li key={person.id}>
<img src={getImageUrl(person)} alit=""/>
<p>
<b>{person.name}</b>
{' ' + person.name + ' ' }
known for { person.accomplishment }
</p>
</li>
};
return (
<article>
<h1>Scientist</h1>
<ul>{listItems}</ul>
</article>
)
Pure - Component
- 只负责自己的任务,不会更改在该函数调用前已经存在的对象或变量
- 输入相同,输出也相同:在输入相同的情况下,对纯函数来说总是返回相同的结果
let guest = 0;
function Cup() {
// Bad: changing a preexisting variable
guest = guest + 1;
return <h2>Tea Cup for guest ${guest}</h2>;
}
export default function TeaSet() {
return (
<>
<Cup/>
<Cup/>
<Cup/>
</>
)
}
// 通过传递props使得组件变得纯粹,而非修改已经有的变量
function Cup({ guest }) {
return <h2>Tea Cup for guest #{guest}</h2>;
}
export default function TeaSet() {
return (
<>
<Cup guest={1}/>
<Cup guest={2}/>
<Cup guest={3}/>
</>
)
}
在JSX中通过大括号使用JS
JSX允许在JS中编写类似HTML的标签,从而使得渲染的逻辑和内容可以融合在一起
- JSX的大括号内引用JS变量
- JSX的大括号内调用JS函数
- JSX的大括号内使用JS对象
export default function Avatar() {
const avatar = 'http://www.baidu.com';
const desc == 'test ---------- ';
const userName = 'Wangz';
return (
<span>userName: {userName}</span>
<img className='avatar' src={avatar} alt={desc}/>
)
}
// 使用“双大括号”:JSX中的CSS和对象
// 除了字符串、数字和其他JS表达式,甚至可以在JSX中传递对象
// 对象也是用大括号表示 eg: {name: "wangzz", age: 24}
export default function TodoList() {
return (
<ul style={{backgroundColor: 'black', color: 'pink'}}>
<li> Improve the videophone</li>
<li> Perpare ae</li>
<li> Test</li>
</ul>
)
}
JSX小结:
- JSX引号内的值会作为字符串传递给属性
- 大括号可以将JS的逻辑和变量带入标签中
- 会在JSX标签中的内容区域或紧随属性的 = 后起到作用
- {{}}不会特殊语法: 只是包含在JSX大括号内的JS对象
将Props传递给组件
React组件会使用props互相同通信,每个父组件都可以提供props给子组件
从而将一些信息传递给子组件,props可传递对象、数组和函数
Props是不可变的,当一个组件需要改变其props的时候
将不得不请求其父组件传递不同的props —— 一个新对象,旧的props会被丢弃,
最终JS引擎会回收他们占用的内存;
不要尝试“更改props",当需要响应用户输入的时候,可以设置”state"可以在
添加交互
界面上的控件会根据用户的输入而更新,
点击按钮切换轮播图的显示,在React中,随着时间变化的数据称之为状态(state)
可以向任何组件添加状态,并按照需求进行更新。
响应事件
React允许向JSX中添加事件处理程序等,事件处理程序即函数,在用户交互的时候触发
eg: click、hover、focus、input、blur等
交互事件
界面控件根据用户的输入而更新,eg: 点击按钮切换轮播图的展示,在React中,随着时间变化的数据被称为状态(state)
可以向任何组件添加状态,并按照需求进行更新。
- 响应事件
function Button({ onClick, children }) {
return (
<button onClick={onClick}>
{children}
</button>
)
}
function Toobar({ onPlayMovie, onUploadImage }){
return (
<Button onClick={onPlayMovie}>
Play Movie
</Button>
<Button onClick={onUploadImage}>
Upload Image
</Button>
)
}
export default function App() {
return (
<Toolbar
onPlayMovie={() => alert('Playing')}
onUploadImage={() => alert('UP}
/>
)
}
State: 组件的记忆
组件通常需要根据交互改变屏幕上的内容,在表单中键入更新输入栏
useState Hook为组件添加状态,Hook能够让组件使用React功能的特殊函数
useState声明一个状态变量,接收初始状态并返回一对值:当前状态以及一个更新状态的设置函数
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
- example
import { useState } from 'react';
import { sculptureList } from './data.js';
export default function Gallery() {
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
const hasNext = index < sculptureList.length - 1;
function handleNextClick() {
if (hasNext) {
setIndex(index + 1);
} else {
setIndex(0);
}
}
function handleMoreClick() {
setShowMore(!showMore);
}
let sculpture = sculpture[index];
return (
<>
<button onClick={handleNextClick}>
Next
</button>
<h2>
<i>{sculpture.name}</i>
by {sculpture.artist}
</h2>
<h3>
({index+1} of {sculptureList.length})
</h3>
<button onClick={handleMore Click}>
{showMore ? 'Hide' : 'show'} details
</button>
<img src={sculpture.url} alt={ssculpture.alt}/>
</>
)
}
渲染和提交
在组件显示在屏幕之前需要由React进行渲染
- 触发渲染(将订单送到厨房)
- 渲染组件(按照订单准备)
- 提交到DOM(将订单送到桌前)
快照的状态
和普通JS变量不同,React状态的行为更像一个快照,设置它不会改变已有的状态变量,而是触发一次重新渲染
console.log(count); // 0
setCount(count + 1); // 请求用1重新渲染
console.log(count); // 0
更新状态中的对象
状态可以持有任何类型的JS值,(对象),★无法直接改变在React状态中持有的对象和数组。
当需要更新一个对象和数组的时候,需要创建一个新的对象(或复制现有的对象)
用这个副本来更新状态。
... 展开语法来赋值想要改变的对象和数组
import { useState } from 'react'
export default function Form() {
const [person, setPerson] = useState({
});
}
什么是mutation?
在state中存放任意类型的JS值
const [x, setX] = useState(0);
setX(5); // 从0 =》 5,数字0本身没有变,JS中无法内置原始值 eg: 数字、字符串和布尔值进行更改
// state存放对象
const [position, setPosition] = useState({x: 0, y: 0});
// 可以改变对象自身的内容,但是会制造一个mutation
position.x = 5;
// 因此你应该替换它们的值,而不是对它们进行修改。
★ 将state视为只读的
应该将所有存放在state中的JS对象都视为只读的
...展开语法本质是是“浅拷贝” —— 只会复制一层,可以提高执行速度
- eg: 使用一个事件处理函数来更新多个字段
import { useState } from 'react'
export default function Form() {
const [person, setPerson] = useState({
firstName: 'Barbara',
lastName: 'Hepworth',
email: 'bheppasdfjpijpia',
});
function handleChange(e) {
setPerson({...person, [e.target.name]: e.target.value});
}
return (
<>
<label>
First name: <input/>
</label>
</>
)
}
更新一个嵌套对象
const [person, setPerson] = useState({
name: 'Niki',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'www.baidu.com',
}
});
// 将state视为不可变的,为了修改city的值,需要创建一个新的artwork对象
const nextArtwork = { ...person.artwork, city: 'NewDelhi'}
const nextPerson = { ...person, artwork: nextArtwork };
setPerson(nextPerson);
// or
setPerson({
...person, // 复制其他字段的数据
artwork: {
...person.artwork, // 复制之前person.artwork中的数据
city: 'New Delhi',
}
});
// ★ 使用Immer编写简介的更新逻辑
// 可以使用Immer刘幸苦来实现更为便捷的改变嵌套展开效果
updatePerson(draft => {
draft.artowork.city = 'Lagos';
});
// Immer提供的draft是一种特殊类型的对象,称之为Proxy
// 会记录所有的擦欧总,Immer会弄清楚draft对象的那些部分改变了,根据修改生成新对象
使用Immer
- npm install use-immer // 添加immer依赖
- import { useImmer } from 'use-immer' => 替换掉import { useState } from 'react'
import { useImmer } from 'use-immer';
export default function Form() {
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;
});
}
function handleTitleChange(e) {
updatePerson(draft => {
draft.artwork.title = e.target.value;
});
}
function handleCityChange(e) {
updatePerson(draft => {
draft.artwork.city = e.target.value;
});
}
function handleImageChange(e) {
updatePerson(draft => {
draft.artwork.image = e.target.value;
});
}
return (
<>
<label>
Name:
<input
value={person.name}
onChange={handleNameChange}
/>
</label>
<label>
Title:
<input
value={person.artwork.title}
onChange={handleTitleChange}
/>
</label>
<label>
City:
<input
value={person.artwork.city}
onChange={handleCityChange}
/>
</label>
<label>
Image:
<input
value={person.artwork.image}
onChange={handleImageChange}
/>
</label>
<p>
<i>{person.artwork.title}</i>
{' by '}
{person.name}
<br />
(located in {person.artwork.city})
</p>
<img
src={person.artwork.image}
alt={person.artwork.title}
/>
</>
);
}
事件处理函数变得更加简洁方便;随意在一个组件中同时使用useState和useImmer
为什么在React中无法推荐直接修改state?
将 React 中所有的 state 都视为不可直接修改的。
当你在 state 中存放对象时,直接修改对象并不会触发重渲染,并会改变前一次渲染“快照”中 state 的值。
不要直接修改一个对象,而要为它创建一个 新 版本,并通过把 state 设置成这个新版本来触发重新渲染。
你可以使用这样的 {...obj, something: 'newValue'} 对象展开语法来创建对象的拷贝。
对象的展开语法是浅层的:它的复制深度只有一层。
想要更新嵌套对象,你需要从你更新的位置开始自底向上为每一层都创建新的拷贝。
想要减少重复的拷贝代码,可以使用 Immer。
更新state中的数组
数组是另外一种存储在state中的JS对象,
slice => 拷贝数组或数组的一部分
splice => 会直接修改原始数组(插入或删除元素
数组正确修改方法
setArtists(
[
...artists, // 新数组包含原数组的所有元素
{id: nextId++, name: name} // 在末尾添加一个新的元素
]
);
从数组中删除元素 =》 filter或map来进行过滤
import { useState } from 'react';
let initialArtists = [
{ id: 0, name: 'Marta Colvin Andrate'},
{ id: 1, name: 'Lamidi Olonde Fakeye'},
{ id: 2, name: 'Louise Nevelson'},
]
export default function List() {
const [artists, setArtists] = useState(initialArtists);
return (
<>
{artists.map(artist => (
<li key={artist.id}>
{artist.name}{' '}
<button onClick={() => {
setArtists(
artists.filter(a =>
a.id !== artist.id
)
);
}}>
删除
</button>
</li>
))}
</>
)
}
转换数组
想要改变数组中的某些或全部元素,可以使用map()创建一个新数组,传入map的函数决定
要根据每个元素的值或索引对元素做出处理
map()和filter()不会直接修改原始数组,reverse()和sort()会改变原始数组
状态管理
=> 数据 =》 组件之间流动
状态响应输入
使用React,无需直接从Code层面修改UI,eg: 不需要编写‘禁用按钮’、‘启用按钮’、‘显示成功消息’等,只需要描述组件在不同状态(初始状态、输入状态、成功状态),
根据用户输入触发状态更改。
import { useState } from 'react'
export default function Form() {
const [anser, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing');
if(status === 'success') {
return <h1>答对了!</h1>
}
async function handleSubmit(e) {
e.preventDefault();
setStatus('submitting');
try {
await submitForm(answer);
setStatus('success');
} catch (err) {
setStatus('typing');
setError(err);
}
}
function handleTextareaChange(e) {
setAnswer(e.target.value);
}
return (
<>
<form onSubmit={handleSubmit}>
<textarea
value={answer}
onChange={handleTextareaChange}
disabled={status === 'submitting'}
/>
<button disabled={answer.length === 0 || status === 'submitting'}>
提交
</button>
{
error !== null && <p className='Error'> {error.message} </p>
}
</form>
</>
)
}
function submitForm(answer) {
// 模拟接口请求
return new Promise((resolve, reject) => {
setTimeout(() => {
let shouldError = answer.toLowerCase() !== 'lima'
if(shouldError) {
reject(new Error('Right'));
} else {
resolve();
}
}, 1500);
});
}
状态结构
// 状态不应该包含冗余或重复的信息
// 如果包含一些多余的状态会忘记去更新,从而导致问题产生
import { useState } from 'react';
export default function Form() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');
function handleFirstNameChange(e) {
setFirstName(e.target.value);
setFullName(e.target.value + '' + lastName);
}
}
在组件之间共享状态
希望两个组件的状态始终同步更改,将相关状态从两个组件上移除,
并将这些状态移动到最近的父级别组件,通过props将状态传递给两个组件
称之为“状态提升"
import { useState } from 'react'
export default function Accordion() {
const [activeIndex, setActiveIndex] = useState(0);
return (
<>
<Panel
title='关于'
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}/>
</>
)
}
提取状态逻辑到reducer中
将需要更新多个状态的组件来说,在组件外部将所有状态更新逻辑合并到一个成为“reducer”的函数
事件处理程序会变得很简洁,只需要指定用户的“actions”,在文件底部,reducer函数指定状态
应该如何更新以响应每个action
import { useReducer } from 'react'
import AddTask from './AddTask.js'
import TaskList from './TaskList.js'
export default function TaskApp() {
const [tasks, dispatch] = useReducer(
tasksReducer,
initialTasks
);
function handleAddTask(text) {
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}
function handleChangeTask(task) {
dispatch({
type: 'changed',
task: task,
})
}
function handleDeleteTask(taskId) {
dispatch({
type: 'deleted',
id: taskId,
});
}
return (
<>
<AddTask onAddTask={handleAddTask}/>
<TaskList
tasks={tasks}
onChangeTask={handleChangeTask}
onDeleteTask={handleDeleteTask}/>
</>
)
}
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('Unknown Operation: ' + action.type);
}
}
}
let nextId = 3;
const initialTasks = [
{ id: 0, text: '参观卡夫卡博物馆', done: true },
{ id: 1, text: '看木偶戏', done: false },
{ id: 2, text: '列侬墙图片', done: false }
];
使用Reducer和Context进行状态扩展
- Reducer: 合并组件的状态更新逻辑
- Context:将信息深入传递给其他组件
利用Reducer和Context组合在一起实现对复杂应用状态的管理
使用reducer来管理一个具有复杂状态的父组件
组件树中任何深度的其他组件都可以通过context读取状态,
再通过dispatch来更新状态