React学习笔记
搭建一个react工程
react是一个渐进式框架,本次学习采用创建react工程的方法进行学习,本次学习使用Create React App脚手架进行项目的初始化
Create React App
创建项目
// my-app是项目名
npx create-react-app my-app
上面一步会直接下载好依赖,直接进目录运行就可以了。
然后就可以通过以下代码进入并运行项目了
cd my-app
npm start
创建组件
jsx语法
JSX 的语法比 HTML 更严格。类似
这样的标签是必须要关闭的。并且,组件也不能返回多个并列最高层级的 JSX 标签,你必须为所有最高层级的标签添加一个共同的父标签,例如使用
<div>...</div> 或 <>...</> 作为父标签:
创建一个普通的组件
请注意, 标签以大写字母开头,这样就能便于识别这个是一个 React 组件。React 组件的名称必须始终以大写字母开头,而 HTML 标签必须全部为小写字母。
文件务必是jsx格式的。
// 创建一个按钮组件
export default function MyButton() {
return (
<button>Click me</button>
);
}
创建一个MyApp组件测试,并引入MyButton组件
import MyButton from './MyBtn';
export default function MyApp() {
return (
<div>
<h1>Welcome to my app</h1>
<MyButton />
</div>
);
}
在 JSX 中使用大括号编写 JavaScript
jsx标签中,可以通过大括号来使用js定义好的变量,我们对MyApp进行改造,代码如下:
import MyButton from './MyBtn';
const hello = {
text:"Welcome to my app2",
theme:{
color:"yellow"
}
}
export default function MyApp() {
return (
<div>
<h1 style={hello.theme}>{hello.text}</h1>
<MyButton />
</div>
);
}
效果
组件间传参props
React 组件使用 props 来进行组件之间的通讯。每个父组件都可以通过为子组件提供 props 的方式来传递信息。props 可能会让你想起 HTML 属性,但你可以通过它们传递任何 JavaScript 的值,包括对象、数组、函数、甚至是 JSX!
修改代码通过MyApp给MyButton传参,
注意:MyButton接参数时需要通过对象接,具体的看代码
// MyApp
import MyButton from './MyBtn';
const hello = {
text:"Welcome to my app2",
theme:{
color:"yellow"
},
btnText:"我是按钮"
}
export default function MyApp() {
return (
<div>
<h1 style={hello.theme}>{hello.text}</h1>
<MyButton text={hello.btnText} />
</div>
);
}
// MyButton
// 注意。这里接受参数用了{},如果有多个也得放大括号里比如{text,num}这样
export default function MyButton({text}) {
return (
<button>{text}</button>
);
}
条件渲染
在 React 中,你可以使用 JavaScript 语法,如 if 语句、&& 和 ? : 操作符有条件地渲染 JSX。
就是jsx的if渲染方式,这里演示一个子组件通过props不通值来条件渲染,代码如下:
function Item({name,isPacked}) {
return (
<li>{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 中使用 JavaScript 的 filter() 和 map() 来实现数组的过滤和转换,将数据数组转换为组件数组。
对于数组的每个元素项,你需要指定一个 key。通常你需要使用数据库中的 ID 作为 key。即使列表发生了变化,React 也可以通过 key 来跟踪每个元素在列表中的位置。
修改刚才的列表组件,代码如下:
注意:一定要设置key
function Item({name,isPacked}) {
return (
<li>{name}{isPacked && "√"}</li>
);
}
const arrObj = [
{name:"Space suit",isPacked:true},
{name:"Helmet with a golden leaf",isPacked:true},
{name:"Photo of Tam",isPacked:false},
]
export default function PackingList() {
// 修改为循环的
const listItem = arrObj.map((i)=>{
return (<Item
isPacked={i.isPacked}
name={i.name}
key={i.name}
/>)
})
return (
<section>
<h1>Sally Ride's Packing List</h1>
<ul>
{listItem}
</ul>
</section>
);
}
添加交互
相应事件
React 允许你向 JSX 中添加事件处理程序。事件处理程序是你自己的函数,它将在用户交互时被触发,如点击、悬停、焦点在表单输入框上等等。
等内置组件只支持内置浏览器事件,如 onClick。但是,你也可以创建你自己的组件,并给它们的事件处理程序 props 指定你喜欢的任何特定于应用的名称。
以下代码定义了onclick触发的逻辑,并且运用了插槽
export default function Toolbar({ onPlayMovie, onUploadImage }) {
return (
<div>
<Button onClick={onPlayMovie}>
Play Movie
</Button>
<Button onClick={onUploadImage}>
Upload Image
</Button>
</div>
);
}
function Button({ onClick, children }) {
return (
<button onClick={onClick}>
{children}
</button>
);
}
// app里使用
import MyButton from './MyBtn';
import PackingList from './PackingList';
import MyToolbar from './MyToolbar';
const hello = {
text:"Welcome to my app2",
theme:{
color:"yellow"
},
btnText:"我是按钮"
}
export default function MyApp() {
return (
<div>
<h1 style={hello.theme}>{hello.text}</h1>
<MyButton text={hello.btnText} />
<PackingList></PackingList>
<MyToolbar onPlayMovie={()=>{alert('Playing!')}} onUploadImage={() => alert('Uploading!')}></MyToolbar>
</div>
);
}
State: 组件的记忆
你可以用 useState Hook 为组件添加状态。Hook 是能让你的组件使用 React 功能的特殊函数(状态是这些功能之一)。useState Hook 让你声明一个状态变量。它接收初始状态并返回一对值:当前状态,以及一个让你更新状态的设置函数。
注意:
- 使用useState Hook时,必须必须将其包在方法中,不能直接写在块里
- const[index,setIndex]=useState(0)返回两个值index是值,setIndex相当于set函数,通过它给setIndex赋值
- index值发生变化时视图刷新。
- 补充,其实调用setIndex方法时,是去请求react按照新值重新渲染页面。但不会在已运行的代码中更改它,也就是在重新渲染前index不变,setIndex(index+1),不管连续调用多少次都是0+1 为1。
代码如下:
import {useState} from 'react';
const list = [{name:"Homenaje a la Neurocirugía"},{name:"Floralis Genérica"},{name:"Eternal Presence"}]
// 注意,使用useState是外层得包一个方法
export default function Gallery(){
// 返回的第一个参数index是目前的值,这里就是0,
// 返回的第二个参数setIndex,可以通过它来修改index的值,比如setIndex(5)后index就变成5了
// 具体应用可以参考下面的handleNextClick方法和handleMoreClick方法
const [index,setIndex] = useState(0);
const [showMore, setShowMore] = useState(false)
// 数组切换的逻辑
const hasNext = index < list.length - 1
function handleNextClick() {
if (hasNext) {
setIndex(index + 1);
} else {
setIndex(0);
}
}
function handleMoreClick() {
setShowMore(!showMore);
}
let sculpture = list[index]
return (
<>
<button onClick={handleNextClick}>
Next
</button>
<h2>
<i>{sculpture.name} </i>
</h2>
<h3>
({index + 1} of {list.length})
</h3>
{/* 这是true和false变化的逻辑部分 */}
<button onClick={handleMoreClick}>
{showMore ? 'Hide' : 'Show'} details
</button>
</>
)
}
作为快照的状态
与普通 JavaScript 变量不同,React 状态的行为更像一个快照。设置它并不改变你已有的状态变量,而是触发一次重新渲染。这在一开始可能会让人感到惊讶!
console.log(count); // 0
setCount(count + 1); // 请求用 1 重新渲染
console.log(count); // 仍然是 0!
React 这样工作是为了帮助你避免微妙的 bug。这里有一个小的聊天应用程序。试着猜一猜,如果先按下“发送”,然后再把收件人改为 Bob,会发生什么?五秒钟后,谁的名字会出现在 alert 中?
// 测试快照的问题
import { useState } from 'react';
export default function Form() {
// 每次值变化时,触发的时重新渲染,
// 推测在渲染时重新调用了Form方法。所以之前调用的handlerSumit的定时器内的变量值不会变。
// 你可以在发送后改变页面中的内容来看到这个现象。
const [to, setTo] = useState('Alice');
const [message, setMessage] = useState('Hello');
function handleSubmit(e) {
e.preventDefault();
setTimeout(() => {
alert(`You said ${message} to ${to}`);
}, 5000);
}
return (
<form onSubmit={handleSubmit}>
<label>
To:{' '}
<select
value={to}
onChange={e => setTo(e.target.value)}>
<option value="Alice">Alice</option>
<option value="Bob">Bob</option>
</select>
</label>
<textarea
placeholder="Message"
value={message}
onChange={e => setMessage(e.target.value)}
/>
<button type="submit">Send</button>
</form>
);
}
更新状态中的对象
状态可以持有任何类型的 JavaScript 值,包括对象。但你不应该直接改变你在 React 状态中持有的对象和数组。相反,当你想更新一个对象和数组时,你需要创建一个新的对象(或复制现有的对象),然后用这个副本来更新状态。
通常情况下,你会使用 … 展开语法来复制你想改变的对象和数组。例如,更新一个嵌套对象可以是这样的:
import { useState } from 'react';
export default function Form() {
// 状态管理中的数据可以是对象,也可以是数组
// 当是对象时,状态发生变化时,不要直接在原对象上修改,因为react要保留每次变更的数据的
// 所以需要我们需要复制该对象,通过传入新的对象来修改状态。
const [person, setPerson] = useState({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
}
});
function handleNameChange(e) {
setPerson({
...person,
name: e.target.value
});
}
function handleTitleChange(e) {
setPerson({
...person,
artwork: {
...person.artwork,
title: e.target.value
}
});
}
function handleCityChange(e) {
setPerson({
...person,
artwork: {
...person.artwork,
city: 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>
<p>
<i>{person.artwork.title}</i>
{' by '}
{person.name}
<br />
(located in {person.artwork.city})
</p>
</>
);
}
更新状态中的数组
数组是另一种可以存在状态中的可变 JavaScript 对象,应将其视为只读。就像对象一样,当你想更新存在状态中的数组时,你需要创建一个新数组(或者复制现有数组),然后用新数组来更新状态。
import { useState } from 'react';
let nextId = 3;
const initialList = [
{ id: 0, title: 'Big Bellies', seen: false },
{ id: 1, title: 'Lunar Landscape', seen: false },
{ id: 2, title: 'Terracotta Army', seen: true },
];
export default function BucketList() {
const [list, setList] = useState(
initialList
);
function handleToggle(artworkId, nextSeen) {
setList(list.map(artwork => {
if (artwork.id === artworkId) {
return { ...artwork, seen: nextSeen };
} else {
return artwork;
}
}));
}
return (
<>
<h1>Art Bucket List</h1>
<h2>My list of art to see:</h2>
<ItemList
artworks={list}
onToggle={handleToggle} />
</>
);
}
function ItemList({ artworks, onToggle }) {
return (
<ul>
{artworks.map(artwork => (
<li key={artwork.id}>
<label>
<input
type="checkbox"
checked={artwork.seen}
onChange={e => {
onToggle(
artwork.id,
e.target.checked
);
}}
/>
{artwork.title}
</label>
</li>
))}
</ul>
);
}
状态管理
随着你的应用不断变大,更有意识的去关注应用状态如何组织,以及数据如何在组件之间流动会对你很有帮助。冗余或重复的状态往往是缺陷的根源。在本节中,你将学习如何组织好状态,如何保持状态更新逻辑的可维护性,以及如何跨组件共享状态。
使用状态响应输入
与vue一样,利用状态判断按钮禁用,显示哪些组件等,只不过可以在方法中直接return不同的jsx来更明显的修改。
选择状态结构
useState变量不要太冗余,比如全名和firstName的显示
import { useState } from 'react';
export default function Form() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
// 可以理解成计算属性?
const fullName = firstName + ' ' + lastName;
function handleFirstNameChange(e) {
setFirstName(e.target.value);
}
function handleLastNameChange(e) {
setLastName(e.target.value);
}
return (
<>
<h2>让我们帮你登记</h2>
<label>
名:{' '}
<input
value={firstName}
onChange={handleFirstNameChange}
/>
</label>
<label>
姓:{' '}
<input
value={lastName}
onChange={handleLastNameChange}
/>
</label>
<p>
你的票将发给:<b>{fullName}</b>
</p>
</>
);
}
在组件间共享状态
有时候你希望两个组件的状态始终同步更改。要实现这一点,可以将相关状态从这两个组件上移除,并把这些状态移到最近的父级组件,然后通过 props 将状态传递给这两个组件。这被称为“状态提升”,这是编写 React 代码时常做的事。
在以下示例中,要求每次只能激活一个面板。要实现这一点,父组件将管理激活状态并为其子组件指定 prop,而不是将激活状态保留在各自的子组件中。
保留和重置状态
通过设置key ,当key变化时对应组件数据就算没变化也会强制重新渲染。和vue的key的用法类似
提取状态逻辑到 reducer 中
对于那些需要更新多个状态的组件来说,过于分散的事件处理程序可能会令人不知所措。对于这种情况,你可以在组件外部将所有状态更新逻辑合并到一个称为 “reducer” 的函数中。这样,事件处理程序就会变得简洁,因为它们只需要指定用户的 “actions”。在文件的底部,reducer 函数指定状态应该如何更新以响应每个 action!
// 将state逻辑抽出去的一个尝试,
// 注意,这里引入的就是useReducer了。
import { useReducer } from 'react';
export default function TaskApp() {
// initialTasks是数据源
// tasksReducer是处理数据源的某个方法,里面可以包含不同的处理数据源的逻辑。
// tasksReducer方法接受两个参数,第一个是useReducer绑定的数据源,这里就是tasks,第二个参数是dispath传入的内容。
// 我们来看useReducer得到的两个变量
// tasks是initialTasks得到的响应式数据源
// dispatch是修改数据源的方法,这里绑定了tasksReducer。那么dispatch传入的参数作为tasksReducer方法第二个参数被传入。且调用dispatch时触发一次数据变动,重新渲染
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
});
}
const listDiv = tasks.map((item,index)=>{
return (
<div key={item.id}>
<span>{item.text}</span>
<input type="checkbox" checked={item.done} onClick={()=>{handleChangeTask({...item,done:!item.done})}}/>
<button onClick={()=>{handleDeleteTask(index)}}>删除</button>
</div>
)
})
return (
<>
<h1>布拉格行程</h1>
{listDiv}
</>
);
}
// 作为useReducer修改数据的方法和useState的注意事项一样,如果是复杂数据类型,搞一份深拷贝的数据再修改后直接返回。
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.type);
}
}
}
let nextId = 3;
const initialTasks = [
{ id: 0, text: '参观卡夫卡博物馆', done: true },
{ id: 1, text: '看木偶戏', done: false },
{ id: 2, text: '列侬墙图片', done: false }
];