# React
- 创建React元素
// 1. 创建React元素
// 参数1:创建的元素名
// 参数2:元素的属性,如果没有,可以为null
// 参数3:元素的子节点或内容
// ...
// 返回值:React元素
const title = React.createElement('h1', null, 'Hello React')
2
3
4
5
6
7
- 渲染React元素
// 参数1:要渲染的react元素
// 参数2:挂载点,需要将react元素渲染到哪儿
ReactDOM.render(title, document.getElementById('root'))
2
3
# setState
setState会重新渲染组件
setState的两种方式
1.对象式
<button onClick={() => {
this.setState({
number: this.state.number + 1
});
console.log(this.state);
}} />
2
3
4
5
6
如果想要在更新完状态后立刻拿到该状态,可以在回调中调用。
this.setState({number: this.state.number + 1}, () => {
console.log(this.state.number);
})
2
3
2.函数式
<button onClick={() => {
this.setState((state) => {
return {
number: state.number + 1
};
});
console.log(this.state);
}} />
2
3
4
5
6
7
8
⚠️注意:this.setState()可能是异步的.
# State 的更新可能是异步的
出于性能考虑,React 可能会把多个
setState()调用合并成一个调用。因为
this.props和this.state可能会异步更新,所以你不要依赖他们的值来更新下一个状态。
# 回调函数this丢失问题
const module = { x: 42, getX: function() { return this.x; } }; console.log(module.getX()); const unboundGetX = module.getX; //this会丢失 console.log(unboundGetX()); // The function gets invoked at the global scope // expected output: undefined const boundGetX = unboundGetX.bind(module); console.log(boundGetX()); // expected output: 421
2
3
4
5
6
7
8
9
10
11
12
13
14解决方法:使用bind()
# React组件中共享state
在 React 中,将多个组件中需要共享的 state 向上移动到它们的最近共同父组件中,便可实现共享 state。这就是所谓的“==状态提升==”。接下来,我们将
TemperatureInput组件中的 state 移动至Calculator组件中去。
# React生命周期
https://segmentfault.com/a/1190000020348448
# 挂载阶段
当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:
constructor()(opens new window) 实例化static getDerivedStateFromProps()(opens new window) 从props中获取staterender()(opens new window) 渲染componentDidMount()(opens new window) 完成挂载
# 更新阶段
当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:
static getDerivedStateFromProps()(opens new window) 从props中获取stateshouldComponentUpdate()(opens new window) 判断是否需要重绘render()(opens new window) 渲染getSnapshotBeforeUpdate()(opens new window) 获取快照componentDidUpdate()(opens new window) 渲染完成后回调
# 卸载阶段:
componentWillUnmount即将卸载。
# 错误处理:
static getDerivedStateFromError从错误中获取 state。componentDidCatch捕获错误并进行处理。(可以存储错误日志)
CodeSandBox (opens new window)
当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:
import React from 'react'
import ReactDOM from 'react-dom'
// 组件的创建阶段(挂载阶段)
// 1. constructor()
// (1) 提供初始化的数据, this.state={ ... }
// (2) 创建 ref this.demoRef = React.createRef() 将来 ref={this.demoRef}
// (3) 处理this指向问题 this.handleClick = this.handleClick.bind(this)
// 2. render()
// 渲染UI, 但是在render中, 请不要调用 setState (死循环)
// 3. componentDidMount() dom更新了
// (1) 发送ajax请求
// (2) dom操作
// 组件的更新阶段(组件重新渲染)
// 1. render() 渲染更新UI视图
// 2. componentDidUpdate() 更新完触发
// 在这两个钩子中, 不要调用 setState (死循环)
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
msg: '嘎嘎',
}
console.log('constructor')
}
render() {
console.log('render')
return (
<div>
<h1>我是app组件</h1>
</div>
)
}
componentDidMount() {
console.log('componentDidMount')
}
componentDidUpdate() {
console.log('componentDidUpdate')
}
}
ReactDOM.render(<App></App>, document.getElementById('root'))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# React Hooks
# useState
# useEffect
useEffect 监听 state变化
const [num, setNum] = useState(1)
useEffect(() => {
console.log('num has changed!', num)
}, [num])
return (
<button onClick={() => setNum(num + 1)}>num+1</button>
)
2
3
4
5
6
7
8
useEffect首次不执行
useEffect的第二个参数,可用来监听依赖的变化。
# useEffect和useLayoutEffect
useEffect可以起到watch的作用
useEffect[callback, dep]
第二个参数为空数组时,callback只会运行一次,在mount的时候
useLayoutEffect 和 useEffect 都是 React Hook 中的函数,用于在组件渲染完成后执行一些副作用操作。
不同之处在于它们执行的时间点不同。
useEffect在组件渲染完成后执行,它是在浏览器绘制完成后异步执行的。这意味着它不会阻塞浏览器的渲染过程,因此适用于不需要同步更新 UI 的副作用操作。useLayoutEffect在组件渲染完成后同步执行,它会在浏览器绘制之前执行。这意味着它会阻塞浏览器的渲染过程,因此在性能敏感的场景下应谨慎使用。使用useLayoutEffect可能会导致用户感知的延迟或者页面不流畅。
# useEffect清理函数用来清理什么
# useEffect清除定时器
在使用 useEffect 的过程中,如果你需要清除定时器,可以使用 useEffect 的返回函数来实现。
示例代码如下:
import { useEffect, useState } from "react";
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
return () => {
clearInterval(intervalId);
};
}, []);
return <div>{count}</div>;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
在这个例子中,我们使用 useEffect 来创建定时器,并且在返回函数中清除定时器。具体来说,我们在 useEffect 中创建了一个 intervalId 变量来存储定时器的 ID,然后在返回函数中使用 clearInterval(intervalId) 来清除定时器。
需要注意的是,清除定时器的操作不会立即执行,而是在组件销毁时才执行。同时,由于我们在依赖项列表中传入了一个空数组,所以这个 useEffect 只会在组件第一次渲染时执行。如果你需要在某个状态值发生变化时重新创建定时器,那么可以将这个状态值作为依赖项传入 useEffect 中。
useEffect(() => {
const fetchData = async () => {
await getDetailInfo(id);
};
if (id) {
fetchData();
}
}, [id]);
2
3
4
5
6
7
8
当 id 不存在时,useEffect 中的回调函数不会被执行,因此不会产生额外的性能开销。
# 取消网络请求
如果你在 useEffect 中发起了网络请求(例如使用 fetch 或 Axios),你应该在清理函数中取消或中断这些请求,以避免不必要的请求和资源浪费。
useEffect(() => {
const request = axios.get('/api/data');
return () => {
request.cancel(); // 取消网络请求
};
}, []);
2
3
4
5
6
7
# Suspense
React Suspense 是 React 中用于处理异步操作的一种机制,它允许组件在等待异步数据时展示一个备用的 UI,而不是阻塞整个 UI 渲染。
// 异步加载的组件
const AsyncComponent = React.lazy(() => import('./AsyncComponent'));
function App() {
return (
<div>
<h1>My App</h1>
{/* 使用 Suspense 包裹需要等待异步操作的组件 */}
<Suspense fallback={<div>Loading...</div>}>
{/* 异步组件,会在加载完成前显示 fallback 内容 */}
<AsyncComponent />
</Suspense>
</div>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# React forwardRef
React 的 forwardRef 是一种用于传递 ref 给子组件的技术。它允许父组件能够访问或操作子组件中的 DOM 元素或组件实例。
使用 forwardRef,您可以在父组件中创建 ref,并将其传递给子组件,从而使父组件能够直接引用子组件的 DOM 元素或实例。
import { forwardRef, useImperativeHandle, useRef } from 'react'
export interface LazyRef {
lazy: () => void
}
interface LazyProps extends React.HTMLAttributes<HTMLDivElement> {
ref?: React.Ref<LazyRef>
onSome?: () => void
}
const Lazy = forwardRef<LazyRef, LazyProps>((props, ref) => {
const divRef = useRef<HTMLDivElement>(null)
useImperativeHandle(ref, () => ({
// 暴露给父组件的方法
lazy: () => {
console.log('say lazy')
}
}))
return (
<div ref={divRef} {...props}>
lazy
</div>
)
})
export default Lazy
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
在使用 ref 获取子组件引用时,有两个注意事项:
- 子组件必须是一个类组件或通过
React.forwardRef创建的函数组件。 - 如果子组件是函数组件,并且未通过
React.forwardRef创建,你无法直接访问它的内部实例或方法。你可以通过在子组件中使用useImperativeHandle钩子将需要暴露给父组件的实例或方法绑定到ref上。
# useImperativeHandle
useImperativeHandle 是 React 中的一个自定义 Hook,它允许您定义在通过 forwardRef 包裹的组件中暴露给父组件的方法或属性。
useImperativeHandle 接受两个参数:ref 和一个回调函数。在回调函数中,您可以返回一个对象,该对象将成为通过 ref 引用的组件的实例。
<Lazy ref={lazyRef} onClick={() => lazyRef.current?.lazy()} />
# createPortal
createPortal 是 React 中的一个函数,用于将子组件渲染到 DOM 树中的指定位置,而不是直接在父组件内部渲染。
createPortal(children, domNode, key?)
# 受控组件和非受控组件
受控组件:
- 可以实时获取和操作表单数据:通过将表单元素的值与状态变量绑定,可以实时获取和操作用户输入的数据。
- 提供更精细的验证和处理逻辑:由于表单数据完全由状态变量控制,您可以在状态变量上执行各种验证和处理逻辑。
- 适用于复杂的表单:对于包含多个字段、依赖关系或动态增减字段的复杂表单,受控组件可以提供更好的可控性和灵活性。
非受控组件:
- 简化代码:非受控组件不需要为每个表单元素都维护一个状态变量,可以简化代码。
- 适用于简单的表单:对于只包含少量简单字段的表单,非受控组件可能更加方便和快速。
- 快速获取表单数据:通过 ref 来访问表单元素,可以在需要的时候快速获取表单数据。
# React组件props设置默认值
const { maxTagNum, maxTagLength } = Object.assign(
{ maxTagNum: 4, maxTagLength: 16 },
{ ...props }
);
2
3
4
这样写略显冗长,可以更简洁地使用解构赋值的默认值
const { maxTagNum = 4, maxTagLength = 16 } = props;
# 高阶组件
定义高阶组件
const withAuth = (Component1: React.ComponentType<any>, requiredPermission: boolean) => (props: any) => {
if (requiredPermission) {
return null
}
return <Component1 {...props} />;
};
const Button: React.FC<{ text: string }> = ({ text }) => (
<button style={{ color: "red" }}>{text}</button>
);
export default () => {
const ButtonWithPermission = withAuth(Button, true);
const ButtonWithoutPermission = withAuth(Button, false);
return (
<>
<span>About</span>
<ButtonWithPermission text='About' />
<ButtonWithoutPermission text='About' />
</>
);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
withAuth有两个连续的箭头函数,该怎么理解呢
第一层箭头函数:
这是一个 高阶函数,它接收一个 React 组件 Component1 作为参数。
返回一个返回值是一个新的函数(箭头函数),本质上是一个React组件。
而React组件的特点就是将所有属性作为props传递,所以text相当于传递给这个函数组件
小技巧,同时使用解构和props
const Button = ({ text, ...props }) => (
<button style={{ color: "red" }} {...props}>{text}</button>
);
2
3
# useState的懒初始化
React 的 useState 支持两种初始化方式:
1.直接传递值
const [items, setItems] = useState(getLocalItems()); // 注意这里直接调用了函数
2.传递函数(懒初始化)
const [items, setItems] = useState(() => getLocalItems()); // 懒初始化
React 只会调用这个函数一次,之后会记住返回的初始值。重新渲染时,不会再调用这个函数。
性能优化:
如果计算初始值的逻辑较重(例如从 localStorage 读取数据、解析 JSON、进行复杂计算等),使用懒初始化可以避免每次渲染都重复执行这段逻辑。