React_doc
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来更新状态
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!