目录
React
react 采用 monorepo 为了更方便的进行版本管理,依赖管理
React jsx
Babel进行编译,借助 @babel/plugin-transform-react-jsx,,转换后生成 React.creactElement方式创建虚拟dom(VNode)
注意点
-
修改组件中的状态(state)时不能直接修改,需要生成新的对象使用setState方法去修改,可以通过use-immer库简化操作
-
hook(例如useState)函数必须在函数组件的最顶层调用,不能在循环、条件判断或者嵌套函数中调用
-
组件初次执行以及组件(或者其祖先之一)的 状态发生了改变会触发一次渲染
-
当你调用 useState 时,React 会为你提供该次渲染 的一张 state 快照 (旧与新的函数组件对比)
-
函数式组件对state进行保留当判断组件显示隐藏 组件的UI位置没改变 就保留组件state; 函数式组件的UI位置改变 就销毁组件重置state
例如:
//会重置 第二个Counter组件
<div>
<Counter />
{showB && <Counter />}
</div>
//会保留 Counter组件的state
<div>
{isFancy ? (
<Counter isFancy={true} />
) : (
<Counter isFancy={false} />
)}
</div>
// 只要在相同位置渲染的是相同组件, React 就会保留状态
Hooks
Hooks 最初是在 React 16.8 中引入的
- useState(initialState) // 设置函数式组件的状态,返回一个状态、修改状态的函数
例如 const [count, setCount] = useState(0)
setCount(1)
// 设置 count 的值为 1
参数可以是任何类型的值
当设置参数为函数时 函数(更新函数)的第一个参数是当前的 state 例如 setCount(count => count + 1)
更新函数必须是 纯函数 并且只 返回 结果
1. 修改对象时
//当状态为对象时 修改对象属性时,不要直接通过 点 . 的方式,而是应该塞入一个新对象。
const [shape, setShape] = useState({
color: 'orange',
position: {
x: 0,
y: 0
}
});
setShape({
...shape,
position: {
x: 1,
y: 2
}
});
2. 修改数组时
不能改变原数组
const [items, setItems] = useState([]);
//添加元素
setItems( // 替换 state
[ // 是通过传入一个新数组实现的
...items, // 新数组包含原数组的所有元素
{ id: nextId++, name: name } // 并在末尾添加了一个新的元素
]
);
//删除元素
setItems( // 替换 state
items.filter(item => item.id !== id) // 通过 filter 方法创建一个新数组
);
//转换数组
//如果你想改变数组中的某些或全部元素,你可以用 map() 根据条件判断创建一个新数组。
//向数组中插入元素
//数组展开运算符 ... 和 数组的 slice() 方法一起使用,方法让你从数组中切出“一片”。为了将元素插入数组
const nextItems = [
...items.slice(0, 1), // 插入点之前的元素:
{ id: nextId++, name: name }, // 新的元素:
...items.slice(1)// 插入点之后的元素:
];
setItems(nextItems);
- useReducer(reducer, initialArg, init?)
用于简化useState逻辑操作的函数,与数组的reduce()方法类似
import { useReducer } from 'react';
function reducer(state, action) {
// ...
}
function MyComponent() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
// ...
}
/*
useReducer() 函数可接受三个参数:
5. reducer 函数,用于处理初始值以及新值的state 和 调用dispatch()方法传来的 action,返回新的state
6. 初始值,可以是对象、数组、数字、字符串等任意类型,用于初始化state
7. 可选参数 init,如果存在,使用 init(initialArg) 的执行结果作为初始值
useReducer() 返回值解析
8. 数组的第一个元素是当前state
9. 第二个元素是dispatch()方法,用于触发reducer函数
注意点
严格模式<StrictMode>下 React 会 调用两次 reducer 和初始化函数
dispatch 函数调用后 是为下一次渲染而更新 state, 在此代码下面拿不到最新值
https://react.docschina.org/reference/react/useReducer#troubleshooting
不应该包含异步请求、定时器或者任何副作用(对组件外部有影响的操作)
执行流程
React 会把当前的 当前的state 和这个 dispatch传入的参数 action 一起作为参数传给 reducer 函数,然后 reducer 计算并返回新的 state,最后 React 保存新的 state,并使用它渲染组件和更新 UI。
*/
- useContext(SomeContext) 用于深层组件之间传值; 传统的办法是使用 props,一层一层把数据向下传递 SomeContext:createContext方法的返回值(上下文对象)
用法:
- 先用 createContext 创建的 context,传入 useContext() 方法中
- 使用
ThemeContext.Provider
组件进行包裹在想要传递的组件的父组件 - 然后在需要获取数据的组件内调用 useContext() 方法 传入上下文对象 SomeContext
import { useContext, createContext } from 'react';
const ThemeContext = createContext('light'); // 参数传入任意类型的值 当做传递默认值
function App() {
const [theme, setTheme] = useState('dark');
const [currentUser, setCurrentUser] = useState({ name: 'Taylor' });
// ...
return (
// value 不传 会使用createContext() 方法传入的默认值
<ThemeContext.Provider value={theme}>
<AuthContext.Provider value={currentUser}>
<Button />
</AuthContext.Provider>
</ThemeContext.Provider>
);
}
function Button() {
const theme = useContext(ThemeContext); // 接收
// ...
}
理解: 可以想象 使用createContext 返回的上下文对象 进行派发值,然后在组件中使用useContext 获取并使用派发值;
- useEffect(setup, dependencies?)
用于处理副作用,比如网络请求、DOM 操作、定时器、订阅事件、和外部变量进行交互等。
可以理解是 React类组件的componentDidMount、componentDidUpdate、componentWillUnmount 的生命周期组合体。
- setup 函数: 处理副作用的函数,函数内部可以返回一个清理函数 执行时机: - 首次渲染时,最新dom树渲染完成后,此时会拿到最新的props和state - 组件更新时,不传依赖项参数时,组件更新 setup函数会再次执行 - 组件销毁时
import { useEffect } from 'react';
useEffect(() => {// setup 函数
return () => {// 清理函数 清理函数 第一次不执行,当前组件再次更新或销毁组件之前执行
}
})
- async/await 使用方式
useEffect(() => {
;(async () => {
await fetch('xxx')
})()
})
- dependencies可选参数: 依赖项数组, setup函数的代码中引用的所有响应式值(props、state)的列表数组。
当传入空数组时,setup函数只会在首次渲染时执行,不会在组件更新时执行。
注意点:
- dependencies参数不是数组时,会出现警告
import { useEffect } from 'react';
useEffect(() => {// setup 函数
return () => {// 清理函数
}
}, [])
当传入依赖项数组时,setup函数会在首次渲染时执行,当依赖项数组中的值发生变化时,setup函数会再次执行。
import { useEffect, useState } from 'react';
const [count, setCount] = useState(0);
useEffect(() => {// setup 函数
return () => {// 清理函数
}
}, [count])
- Effect是如何模拟生命周期的? ```jsx import { useEffect,useState } from ‘react’;
const [count, setCount] = useState(() => {
console.log(‘getDerivedStateFromProps’) return 0; }); useEffect(() => { // TODO: 请求数据 console.log(‘componentDidMount’) return () => { //TODO: 解除事件监听 } }, [])
useEffect(() => {
console.log(‘componentWillReceiceProps’) },[count])
useEffect(() => {
console.log('componentDidUpdate')
})
```
7. useRef(initialValue) 可用于获取dom或者定义一个不会触发渲染的响应式变量
initialValue:ref 对象的 current 属性的初始值。可以是任意类型的值。这个参数在首次渲染后被忽略
useRef 返回值只有一个属性的对象:
- current:ref 对象的当前值。不参与页面的响应式渲染,禁止在标签中使用
用法:
import { useRef } from 'react'
const countRef = useRef(0) // countRef 是一个对象 {current: 0}
注意点:
- 改变 ref中的current属性的值, 不会触发组件重新渲染。
- 相当于定义了一个普通变量
- useMemo(callback, dependencies) 用于缓存计算结果,避免重复计算。(与vue中的computed功能类似)用于性能优化 callback:一个函数,会根据依赖项数组中的值,返回一个缓存结果。 dependencies:依赖项数组,当依赖项数组中的值发生变化时,callback函数会重新执行。 返回值:callback函数的返回值。 - 跳过代价昂贵的重新计算
import { useMemo } from 'react';
function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
}
默认情况下,React 会在每次重新渲染时重新运行整个组件。 如果计算速度很快,这将不会产生问题。但是,当正在过滤转换一个大型数组,或者进行一些昂贵的计算,而数据没有改变,那么可能希望跳过这些重复计算。
如果 todos 与 tab 都与上次渲染时相同,那么像上面那样将计算函数包装在 useMemo 中,便可以重用已经计算过的 visibleTodos。
这种缓存行为叫做 记忆化。
- 跳过组件的重新渲染
默认情况下,当一个组件重新渲染时,React 会递归地重新渲染它的所有子组件。 用 useMemo 和 memo 跳过重新渲染 子组件
import { memo } from 'react';
const List = memo(function List({ items }) {
// ...
});
父组件
export default function TodoList({ todos, tab, theme }) {
// 告诉 React 在重新渲染之间缓存你的计算结果...
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab] // ...所以只要这些依赖项不变...
);
return (
<div className={theme}>
{/* ... List 也就会接受到相同的 props 并且会跳过重新渲染 */}
<List items={visibleTodos} />
</div>
);
}
或者记忆单个jsx节点
export default function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
const children = useMemo(() => <List items={visibleTodos} />, [visibleTodos]);
return (
<div className={theme}>
{children}
</div>
);
}
- useCallback(callback, dependencies) 用于缓存回调函数,避免重复创建。(弥补useMemo对于函数处理嵌套过深) callback:想要缓存的函数。 dependencies:依赖项数组,当依赖项数组中的值发生变化时,callback函数会重新执行,返回一个新的函数引用。 返回值:返回你已经传入的 callback 函数,依赖项(dependencies)改变会返回新的
export default function Page({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}, [productId, referrer]);
return <Form onSubmit={handleSubmit} />;
}
```