docs: React note update

This commit is contained in:
ZiuChen 2023-04-21 23:53:36 +08:00
parent ec8ccbfe39
commit 4f8d95f322

View File

@ -144,7 +144,7 @@ root.render(<App />)
- 当数据发生变化,可以调用 `this.setState` 来更新数据通知React执行视图更新
- update操作时会重新调用render函数使用最新的数据来渲染界面
:::success
::: tip
需要注意的是在constructor中我们调用了`super`因为App类是继承自React.Component类调用`super`即调用了其父类的构造函数让我们的App组件可以继承一些内置属性/方法如`state setState render`
:::
@ -2487,6 +2487,8 @@ const CustomInput = forwardRef((props, ref) => {
在React中HTML表单的处理方式和普通DOM元素不太一样表单通常会保存在一些内部的state中并且根据用户的输入进行更新
下例中创建了一个非受控组件React只能被动从组件接受值并更新到state中而无法主动更新组件的值
```tsx
// Input.jsx
import React, { PureComponent } from 'react'
@ -2517,11 +2519,426 @@ export default class Input extends PureComponent {
}
```
我们对例子稍加改动,将组件的`value`属性设置为state中的值从而实现受控组件。
需要注意的是,绑定`value`属性的同时,我们也要绑定`onChange`事件供用户输入时对state进行更新
```tsx {8,22}
// Input.jsx
import React, { PureComponent } from 'react'
export default class Input extends PureComponent {
constructor(props) {
super(props)
this.state = {
value: 'default Value'
}
}
handleInputChange = (ev) => {
this.setState({
value: ev.target.value
})
}
render() {
return (
<div>
<h2>currentValue: {this.state.value}</h2>
<input type="text" value={this.state.value} onChange={this.handleInputChange} />
</div>
)
}
}
```
this.state.value默认值 => 渲染到Input标签内 => 用户输入 => 触发onChange事件 => 更新state => 渲染到Input标签内 => ...
React要求我们要么指定`onChange`要么指定`readOnly`,只绑定`value`属性时,控制台会抛出错误
### 使用受控组件的几个例子
下例中分别使用`input`创建了几个受控组件,文本框、单选、多选
```tsx
import React, { PureComponent } from 'react'
export default class Form extends PureComponent {
constructor(props) {
super(props)
this.state = {
username: 'ziu',
password: '123456',
isAgree: false,
hobbies: [
{ value: 'sing', label: 'Sing', isChecked: false },
{ value: 'dance', label: 'Dance', isChecked: false },
{ value: 'rap', label: 'Rap', isChecked: false },
{ value: 'music', label: 'Music', isChecked: false }
],
fruits: ['orange']
}
}
handleInputChange = (ev, idx) => {
this.setState({
[ev.target.name]: ev.target.value
})
}
handleAgreeChange = (ev) => {
this.setState({
isAgree: ev.target.checked
})
}
handleHobbyChange = (ev, idx) => {
const hobbies = [...this.state.hobbies] // IMPORTANT
hobbies[idx].isChecked = ev.target.checked
this.setState({
hobbies
})
}
handleSelectChange = (ev) => {
this.setState({
fruits: [...ev.target.selectedOptions].map((opt) => opt.value)
})
}
handleSubmitClick = () => {
const { username, password, isAgree, hobbies, fruits } = this.state
console.log(
username,
password,
isAgree,
hobbies.filter((h) => h.isChecked).map((h) => h.value),
fruits
)
}
render() {
const { username, password, isAgree, hobbies, fruits } = this.state
return (
<div>
<input type="text" value={username} onChange={this.handleInputChange} />
<input type="password" value={password} onChange={this.handleInputChange} />
<div>
<label htmlFor="agree">
Agree
<input id="agree" type="checkbox" checked={isAgree} onChange={this.handleAgreeChange} />
</label>
</div>
<div>
Hobby:
{hobbies.map((hobby, idx) => (
<label key={idx} htmlFor={hobby.value}>
<input
id={hobby.value}
type="checkbox"
checked={hobby.isChecked}
onChange={(ev) => this.handleHobbyChange(ev, idx)}
/>
{hobby.label}
</label>
))}
</div>
<div>
<select value={fruits} onChange={this.handleSelectChange} multiple>
<option value="apple">Apple</option>
<option value="orange">Orange</option>
<option value="banana">Banana</option>
</select>
</div>
<div>
<button onClick={this.handleSubmitClick}>Submit</button>
</div>
</div>
)
}
}
```
这里有一点小知识,关于可迭代对象,可以通过`Array.from`将可迭代对象转为数组
方便我们使用数组的方法来操作选取的DOM列表
简单做一下总结如何在React中绑定受控组件
| Element | Value Property | Change Callback | New Value in Callback |
| ------- | -------------- | --------------- | --------------------- |
| `<input type="text">` | value | onChange | event.target.value |
| `<input type="checkbox">` | checked | onChange | event.target.checked |
| `<input type="radio">` | checked | onChange | event.target.checked |
| `<textarea>` | value | onChange | event.target.value |
| `<select>` | value | onChange | event.target.value |
在大多数情况下我们都应该使用受控组件来实时获取最新的组件状态组件的状态也维护在React中
如果要使用非受控组件来处理表单
- 数据由DOM元素来处理
- 给DOM节点传入`defaultValue`/`defaultChecked`来为其设置初始值
- 监听`onChange`事件来获取最新的DOM状态
本质上就是在操作DOM而不是在操作React状态
## React的高阶组件
React Hooks更优秀
什么是高阶函数?
- 接受一个或多个函数作为输入
- 输出一个函数
在之前的JS开发中我们经常遇到的几种高阶函数`arr.map` `arr.filter` `arr.reduce` ...
那么什么是高阶组件?
- 高阶组件 Higher Order Components(HOC)
- 高阶组件是参数为组件,返回值为新组件的函数
在React中高阶组件就是一个函数**接受一个组件作为输入,输出一个新的组件**随着React Hooks的出现高阶组件的使用已经不是那么常见了
下面写一个高阶组件的例子:
HelloWorld组件传入一个`name`属性,高阶组件`hoc`会在`HelloWorld`组件的基础上添加一个`name`属性,值为`additional-name-value`
这里的`hoc`就是一个高阶组件,它接受一个组件`HelloWorld`作为输入,返回一个新的组件`NewHelloWorld`
我们可以利用高阶组件来实现一些功能:
- 代码复用
- 渲染劫持
- 状态抽象和变更监听
```tsx
import React, { Component } from 'react'
function hoc(WrapperComponent) {
function NewCpn() {
return <WrapperComponent name="additional-name-value"></WrapperComponent>
}
return NewCpn
}
class HelloWorld extends Component {
render() {
return <div>hello {this.props.name}</div>
}
}
const NewHelloWorld = hoc(HelloWorld)
export default class App extends Component {
render() {
return (
<div>
<NewHelloWorld></NewHelloWorld>
</div>
)
}
}
```
- 高阶组件并不是React API的一部分它是基于React的组合特性而形成的设计模式
- 高阶组件在一些React第三方库中非常常见
- 比如Redux中的connect
- 比如React-Router中的withRouter
### 高阶组件的应用场景(一)
下面我们介绍一个高阶组件的适用场景:
假设我们有userInfo中的若干数据`{ isDarkMode: false, hasLogin: false }`,我们希望这些状态能够在若干组件中共享
下例中我们实现了一个高阶函数`enhancedWithUserInfo`,所有传入它的组件都会被注入`userInfo`这个状态
```tsx
function enhancedWithUserInfo(oldComponent) {
class EnhancedComponent extends PureComponent {
constructor() {
super()
this.state = {
userInfo: {
isDarkMode: false,
hasLogin: false
}
}
}
render() {
return <oldComponent {...this.state.userInfo} />
}
}
return EnhancedComponent
}
```
后续如果有其他组件希望能够共享`userInfo`这个状态时,只需要将其传入`enhancedWithUserInfo`
这样我们可以在组件中使用这个状态
```tsx
const NavTabWithUserInfo = enhancedWithUserInfo(
function NavTab(props) {
return (
<div>
isDarkMode: {props.isDarkMode.toString()}
hasLogin: {props.hasLogin.toString()}
</div>
)
}
)
```
高阶组件结合Context我们可以将原来`Context.Consumer`较为繁琐的用法变得十分简单:
在没有高阶组件之前,要使用`Context`则不得不在每个要使用`Context`的组件中,通过`Context.Comsumer`将组件包裹
我们可以封装一个高阶组件`enhanceWithContext`,在组件中统一使用`Context.Consumer`包裹组件并将context中的状态注入到其中
```tsx
// enhanceWithContext.jsx
function enhanceWithContext(OriginComponent) {
return (props) => {
return (
<DarkModeContext.Consumer>
{(context) => <OriginComponent {...context} {...props} />}
</DarkModeContext.Consumer>
)
}
}
```
这样就可以更方便地使用`Context.Consumer`此时context中的状态作为props传递给了低阶组件
在低阶组件中可以很方便的从props中取出context中的状态
```tsx
// App.jsx
import { NavTabWithContext } from './component/NavTab'
export default class App extends Component {
render() {
return (
<div>
<DarkModeContext.Provider>
<NavTabWithContext />
</DarkModeContext.Provider>
</div>
)
}
}
// NavTab.jsx
export class NavTab extends PureComponent {
render() {
return (
<div>
<h1>NavTab</h1>
<p>DarkMode: {this.props.darkMode ? 'true' : 'false'}</p>
</div>
)
}
}
export const NavTabWithContext = enhanceWithContext(NavTab)
```
### 高阶组件的应用场景(二)
假设我们需要计算组件的渲染时间,可以通过计算`UNSAFE_componentWillMount``componentDidMount`的执行时间差值,得到组件的渲染时间
:::warn
目前React已经不推荐在实际开发中使用`UNSAFE_componentWillMount`
:::
下面展示了通过这种方式计算一个大列表的渲染时间的例子:
```tsx
class GiantList extends PureComponent {
constructor() {
super()
this.state = {
list: new Array(100).fill('item')
}
}
UNSAFE_componentWillMount() {
this.beginRender = new Date().getTime()
}
componentDidMount() {
const renderInterval = new Date().getTime() - this.beginRender
console.log('renderInterval', renderInterval)
}
render() {
return (
<div>
<ul>
{this.state.list.map((item, idx) => (
<li key={idx}>{item}</li>
))}
</ul>
</div>
)
}
}
```
我们可以借助高阶组件,将计算渲染时间的能力抽离出来,让我们更方便的在项目中使用:
如果要为组件增强计算渲染时间的能力,只需要将其传入`enhanceRenderInterval`函数即可
```tsx
// enhanceRenderInterval.jsx
export function enhanceRenderInterval(WrappedComponent) {
return class extends WrappedComponent {
UNSAFE_componentWillMount() {
this.beginRender = new Date().getTime()
}
componentDidMount() {
const renderInterval = new Date().getTime() - this.beginRender
// class/function component both has `name` property
console.log(WrappedComponent.name, 'renderInterval', renderInterval)
}
}
}
// List.jsx
const ListWithEnhanceRenderInterval = enhanceRenderInterval(
class List extends PureComponent {
render() {
return (
<ul>
{Array.from({ length: 100 }).map((_, index) => (
<li key={index}>{index}</li>
))}
</ul>
)
}
}
)
```
- 这里使用到了组件的`.name`属性这并不是React提供的API而是原生JS的特性类和函数都有这样一个`name`属性
- 这里封装的高阶组件由于用到了生命周期所以只适用于类组件对于函数式组件有Hooks的实现方案
在历史上React也使用过Mixin作为代码复用的解决方案但是Mixin会导致一系列的问题
- Mixin之间可能会相互依赖、相互耦合不利于代码维护
- 不同的Mixin中的变量/方法会发生冲突
- 多个Mixin混入在一起时组件内部状态会变得更复杂往往导致牵一发而动全身使组件难以维护
利用高阶组件可以针对某些需要高频使用的React代码进行高效的复用但是HOC也有一些缺陷
- HOC需要在原组件上进行包裹或嵌套如果大量使用HOC会产生很多嵌套关系让调试变得复杂
- HOC可以劫持props但在不遵守约定的情况下也可能造成冲突
在React18推出的Hooks是开创性的它解决了之前代码复用存在的很多问题后续会介绍
Mixin已经基本不再适用现代开发基本采用Hooks部分老代码还在使用高阶组件`connect()()`
## portals和fragment
## StrictMode 严格模式