目录
react组件
- 在 React 应用程序中,每一个 UI 模块都是一个组件。
- React 是常规的 JavaScript 函数,只是需要注意
- 它们的名字总是以大写字母开头。
- 它们返回 JSX 标签。
react解析值
以“{}”的方式解析值
- 在 JSX 的大括号内引用 JavaScript 变量
- 在 JSX 的大括号内调用 JavaScript 函数
- 在 JSX 的大括号内使用 JavaScript 对象
props传值
<Avatar
person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
size={100}
/>
function Avatar({ person, size }) {
// 在这里 person 和 size 是可访问的
}
条件渲染
可以在函数组件内通过if 、switch、三元表达式或&& 、 || 来选择性的渲染jsx
列表渲染
通过数组循环对jsx进行输出,如filter、map、find,需要注意的是需要为循环的根节点赋值key,这样react在diff异步算法的时候才会正确的对dom进行更新
响应事件
通过传入事件处理函数进行事件 onClick={handleClick} 响应,也可以onClick={() => { todoSometing()}}这样把函数写入行内,传入的函数一般以handleSomething命名。
state状态管理
export default function Gallery() {
let index = 0;
function handleClick() {
index = index + 1;
}
return (
<div>{index}</div>
)
}
上述index在每次click的后虽然加了1,函数重新执行,但是并不会重新渲染视图,因为React 没有意识到它需要使用新数据再次渲染组件。
通过引入useState为当前组件创建状态
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 1);
setNumber(prevIndex => prevIndex + 1)
setTimeout(() => {
alert(`number ${number} `);
}, 5000); number的值是当前state状态下的值,而不是下次更新后的值
}}>+5</button>
</>
)
}
设置number为初始值0,在准备渲染的时候,值更为1,然后执行setNumber(prevIndex => prevIndex + 1), 传入一个回调函数,prevIndex是当前状态的值,值会更为2, react内部会把这些更新状态的异步任务加入一个队列中,等到事件处理函数中的所有代码都运行完毕再处理你的 state 更新。
另外alert(number);的值不是更新后的值,是当前这一状态的number值,一个 state 变量的值永远不会在一次渲染的内部发生变化, 即使其事件处理函数的代码是异步的,比如
setTimeout(() => {
alert(`number ${number} `);
}, 5000); number的值是当前state状态下的值,而不是下次更新后的值
它的值在 React 通过调用你的组件“获取 UI 快照”(当前state下的UI视图) 时就被“固定”了。
更新state对象
state对象规定是只读的,无法通过调用属性修改当前对象的属性值,只能通过对象覆盖的方式对当前属性进行一个修改,可以用"...展开"修改属性值比如,但是展开只是浅拷贝,如果嵌套多层用,需要多层展开,这是因为state是不可变的,必须传入一个全新的对象来覆盖当前的state。
使用immer更新对象
运行 npm install use-immer
添加 Immer 依赖
immer是一个非常流行的库,它可以让你使用简便但可以直接修改的语法编写代码,并会帮你处理好复制的过程。通过使用 Immer,你写出的代码看起来就像是你“打破了规则”而直接修改了对象:
updateData(draft => {
draft.obj.num = 2;
});
更新state数组
数组同样视为不可变,所以无法对数组内的值通过索引的方式进行修改,任何改变当前数组结构的方法都是不可行的比如pop,push,shift,unshift,splice,reverse,可以通过传入一个新数组来达到修改state数组的目的,可以在原数组的基础上,使用[new, ...arr, new]展开在前后插入,也可以用map,filter,slice返回一个新数组。
使用immer更新数组,更为快捷简便
import { useState } from 'react';
import { useImmer } from 'use-immer';
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 [myList, updateMyList] = useImmer(
initialList
);
const [yourList, updateYourList] = useImmer(
initialList
);
function handleToggleMyList(id, nextSeen) {
updateMyList(draft => {
const artwork = draft.find(a =>
a.id === id
);
artwork.seen = nextSeen;
});
}
function handleToggleYourList(artworkId, nextSeen) {
updateYourList(draft => {
const artwork = draft.find(a =>
a.id === artworkId
);
artwork.seen = nextSeen;
});
}
return (
<>
<h1>艺术愿望清单</h1>
<h2>我想看的艺术清单:</h2>
<ItemList
artworks={myList}
onToggle={handleToggleMyList} />
<h2>你想看的艺术清单:</h2>
<ItemList
artworks={yourList}
onToggle={handleToggleYourList} />
</>
);
}
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>
);
}
state的构建原则
- 合并关联state
- 避免冗余的state,如果能从props或者state计算出来的值,亦或者是两者一起其算出来的值,这个值就应避免出现在state中
- 避免重复的state
- 避免深层嵌套的state。
总之就是让“让状态尽可能简单,但不要过于简单。”
组件状态共享
抽离子组件公共的state,为其父组件添加state传入子组件中,达到共享。
state状态保留
React 根据你的 JSX 生成 UI 树。React DOM 根据 UI 树去更新浏览器的 DOM 元素, state 被保存在 React 内部。根据组件在 UI 树中的位置,React 将它所持有的每个 state 与正确的组件关联起来。
相同位置的相同组件state会被保留
相同位置不同组件会使state重置
其实state重置与否并不是在dom中的位置而是在UI 树,要理解这一点
{isFancy ? (
<Counter isFancy={true} />
) : (
<Counter isFancy={false} />
)}
上面代码是在UI树同一位置
{isPlayerA &&
<Counter person="Taylor" />
}
{!isPlayerA &&
<Counter person="Sarah" />
}
这一段代码就不是,而是
但是如果想让同一位置重置state,则赋予不同的key,就可让react以为是不同的组件。
Reducer的使用
如果一个组件逻辑非常多,对于setState的代码就会很分散,更不利于维护,可以将相对应的逻辑整合到reducer函数中,它的主要作用就是返回最新state状态,接受两个参数,分别为当前 state 和 action 对象,并且返回的是更新后的 state,当然state和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 (
<>
<h1>布拉格的行程安排</h1>
<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('未知 action: ' + action.type);
}
}
}
let nextId = 3;
const initialTasks = [
{id: 0, text: '参观卡夫卡博物馆', done: true},
{id: 1, text: '看木偶戏', done: false},
{id: 2, text: '打卡列侬墙', done: false}
];
上面代码中,tasks为initialTasks初始值,dispatch主要就是执行tasksReducer函数,传入action去更新state的状态。
reducer的实现原理
export function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}
return [state, dispatch];
}
Context上下文
通常来说,你会通过 props 将信息从父组件传递到子组件。但是,如果你必须通过许多中间组件向下传递 props,或是在你应用中的许多组件需要相同的信息,传递 props 会变的十分冗长和不便。Context 允许父组件向其下层无论多深的任何组件提供信息,而无需通过 props 显式传递。
context.js 创建上下文
import { createContext } from 'react';
export const ImageSizeContext = createContext(500);
APP.js
import { useState, useContext } from 'react';
import { places } from './data.js';
import { getImageUrl } from './utils.js';
import { ImageSizeContext } from './Context.js';
export default function App() {
const [isLarge, setIsLarge] = useState(false);
const imageSize = isLarge ? 150 : 100;
return (
<ImageSizeContext.Provider
value={imageSize}
>
<List>
<label>
<input
type="checkbox"
checked={isLarge}
onChange={e => {
setIsLarge(e.target.checked);
}}
/>
Use large images
</label>
<hr />
<List />
</ImageSizeContext.Provider>
)
}
function List() {
const listItems = places.map(place =>
<li key={place.id}>
<Place place={place} />
</li>
);
return <ul>{listItems}</ul>;
}
function Place({ place }) {
return (
<>
<PlaceImage place={place} />
<p>
<b>{place.name}</b>
{': ' + place.description}
</p>
</>
);
}
function PlaceImage({ place }) {
const imageSize = useContext(ImageSizeContext);
return (
<img
src={getImageUrl(place)}
alt={place.name}
width={imageSize}
height={imageSize}
/>
);
}
通过引入上下文使用<nameContext.Provider> 对子组件进行包裹,其中的子组件就可以使用
const xxx= useContext(nameContext);
获取到上层的组件传过来的值
ref
想让组件记住一些信息,但是又不想其触发渲染,可以选用ref
export default function Counter() {
let ref = useRef(0);
function handleClick() {
ref.current = ref.current + 1;
alert('你点击了 ' + ref.current + ' 次!');
}
return (
<button onClick={handleClick}>
点击我!
</button>
);
}
ref操作dom
import { useRef } from 'react';
export default function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} />
<button onClick={handleClick}>
聚焦输入框
</button>
</>
);
}
Effect
useEffect
是 React 中的一个 Hook,用于处理副作用(side effects)在下一次渲染后执行。副作用是指那些不直接与组件渲染相关的操作,比如数据获取、订阅、手动 DOM 操作等。useEffect
允许你在组件渲染完成后执行这些操作,以及在组件卸载前清理这些操作,以避免内存泄漏和其他问题。
useEffect
接受两个参数:一个函数和一个依赖数组。函数参数是要执行的副作用操作,依赖数组是一个可选的参数,它用于指定在哪些依赖变化时触发副作用操作。
import React, { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// 这里可以执行副作用操作,比如数据获取、订阅等
return () => {
// 这里可以执行清理操作,比如取消订阅、清除定时器等
};
}, [/* 依赖 */]);
return (
// 组件的渲染内容
);
}