React18 - 组件化开发(四)
受控/非受控组件
在React中,HTML表单的处理方式和普通的DOM元素不太一样: 表单元素通常会保存在一些内部的state
在 HTML 中,表单元素(如<input>、 <textarea> 和 <select>
)之类的表单元素通常自己维护 state,并根据用户输入进 行更新
在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新
我们将两者结合起来,使React的state成为“唯一数据源”, 渲染表单的 React 组件还控制着用户输入过程中表单发生的操作
被 React 以这种方式控制取值的表单输入元素就叫做“受控组件“
由于在表单元素上设置了 value 属性,因此显示的值将始终为 this.state.value,这使得 React 的 state 成为唯一数据源
import React, { PureComponent } from 'react'
export class App extends PureComponent {
constructor() {
super()
this.state = {
value: ''
}
}
render() {
return (
<div>
{/*
对于这类input input输入的内容是由原生的input进行维护的
并不是由react的state来进行管理的
所以这类组件就被称之为非受控组件
*/}
<input onChange={e => this.getInputVaule(e)} />
<div>{ this.state.value }</div>
</div>
)
}
getInputVaule(e) {
this.setState({
// 虽然react事件中的e是react的合成事件对象
// 但是依旧可以通过e.target.value获取到对应输入的值
value: e.target.value
})
}
}
export default App
import React, { PureComponent } from 'react'
export class App extends PureComponent {
constructor() {
super()
this.state = {
value: 'default value'
}
}
render() {
return (
<div>
{/*
在表单元素上设置了 value 属性,因此显示的值将始终为 this.state.value,这使得 React 的 state 成为唯一数据源
我们修改value值只能通过触发对应的onChange事件,如果只设置了value属性值但是没有绑定onChange事件,表单组件将变成只读组件
在 React 中,可变状态(mutable state)保存在组件的 state 属性中,并且只能通过使用 setState()来更新
这类表单组件就被称之为受控组件
*/}
<input value={this.state.value} onChange={e => this.getInputVaule(e)} />
<div>{ this.state.value }</div>
</div>
)
}
getInputVaule(e) {
this.setState({
value: e.target.value
})
}
}
export default App
import React, { PureComponent } from 'react'
export class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
name: 'Klaus',
isAgree: false,
singleFruit: '',
multFruits: [],
hobbies: [
{ value: 'basketball', label: '篮球', isChecked: false },
{ value: 'football', label: '足球', isChecked: false },
{ value: 'tennis', label: '网球', isChecked: false },
],
fruits: [
{ value: 'apple', label: 'apple', isSelected: false },
{ value: 'orange', label: 'orange', isSelected: false },
{ value: 'banana', label: 'banana', isSelected: false },
]
}
}
handleSubmit(e) {
// 阻止表单的默认提交行为
e.preventDefault()
const { name, isAgree, hobbies, singleFruit, multFruits } = this.state
console.log('name', name)
console.log('isAgree', isAgree)
console.log('singleFruit', singleFruit)
console.log('multFruits', multFruits)
const checkedHobbies = hobbies.filter(hobby => hobby.isChecked).map(hobby => hobby.value)
console.log('checkedHobbies', checkedHobbies)
}
handleChange(e) {
// 通过e.target.type来判断input是文本输入框还是单选按钮
// 如果是文本输入框和下拉单选列表 对应的值 位于 e.target.value
// 对于单选按钮 对应的值 位于 e.target.checked
// 对应的属性名可以通过e.target.name来获取,并通过计算属性名来进行设置
this.setState({
[e.target.name]: e.target.type === 'checkbox' ? e.target.checked : e.target.value
})
}
handleHobbyChange(index) {
const hobbies = [...this.state.hobbies]
hobbies[index].isChecked = !hobbies[index].isChecked
this.setState({ hobbies })
}
handleSelectChange(e) {
this.setState({
// e.target.selectedOptions 可以获取多选下拉列表中,所有被选中的元素项 类别为类数组对象
// 所有的类数组对象 都是可迭代对象
// Array.from(可迭代对象,mapFn)
multFruits: Array.from(e.target.selectedOptions, item => item.value)
})
}
render() {
const { name, isAgree, hobbies, singleFruit, multFruits, fruits } = this.state
return (
<form onSubmit={ e => this.handleSubmit(e) }>
<div>
<label htmlFor="name">
姓名<input type="text" id='name' name='name' value={ name } onChange={ e => this.handleChange(e) } />
</label>
</div>
<div>
<label htmlFor="isAgree">
<input type="checkbox" id='isAgree' name='isAgree' checked={ isAgree } onChange={ e => this.handleChange(e) } />
同意协议
</label>
</div>
<div>
{/* 多选框 */}
爱好:
{
hobbies.map((hobby, index) => (
<label
htmlFor={ hobby.value }
key={hobby.value}
>
<input
type="checkbox"
id={ hobby.value }
checked={ hobby.isChecked }
onChange={ () => this.handleHobbyChange(index) }
/>
{ hobby.label }
</label>
))
}
</div>
<div>
<select name='singleFruit' value={singleFruit} onChange={ e => this.handleChange(e) }>
{
fruits.map(fruit => (
<option
key={ fruit.value }
value={ fruit.value }
>
{ fruit.label }
</option>
))
}
</select>
</div>
{/* 多选下拉列表 */}
<div>
<select multiple value={multFruits} onChange={ e => this.handleSelectChange(e) }>
{
fruits.map(fruit => (
<option
key={ fruit.value }
value={ fruit.value }
>
{ fruit.label }
</option>
))
}
</select>
</div>
<div>
<button type='submit'>smbmit</button>
</div>
</form>
)
}
}
export default App
React推荐大多数情况下使用 受控组件 来处理表单数据
因为在一个受控组件中,表单数据是由 React 组件来统一进行管理
而非受控组件,表单数据将交由 DOM 节点来处理,并不是交给React来统一进行管理
非受控组件在绑定默认值的时候
标签 | 默认值 |
---|---|
<select> 和 <textarea> | defaultValue |
<input type="checkbox"> 和 <input type="radio"> | defaultChecked |
import React, { PureComponent } from "react";
export class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
name: "Klaus"
};
}
handleChange(e) {
this.setState({
name: e.target.value
});
console.log(this.state.name);
}
render() {
const { name } = this.state;
return (
<div>
<input
type="text"
defaultValue={name}
onChange={(e) => this.handleChange(e)}
/>
</div>
);
}
}
export default App;
模拟实现v-lazy效果
在react中的onChange事件,会在输入内容发生改变的时候,实时更新对应的状态
类似于原生的oninput事件,和原生的onchange事件行为正好相反
import React, { PureComponent } from 'react'
export class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
username: ''
}
}
render() {
return (
<div>
<input type="text" value={this.state.username} onChange={e => this.handleChange(e)} />
<div>{ this.state.username }</div>
</div>
)
}
handleChange(e) {
this.setState({
username: e.target.value
})
}
}
export default App
如果我们希望在输入的时候可以实现类似于vue中lazy修饰符的功能的时候
就不可以将对应表单元素设置为受控组件,因为受控组件必须实现onChange事件
而为了实现类似于lazy修饰符的功能,我们需要使用onBlur事件
import React, { PureComponent } from 'react'
export class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
username: ''
}
}
render() {
return (
<div>
<input type="text" defaultValue={this.state.username} onBlur={e => this.handleChange(e)} />
<div>{ this.state.username }</div>
</div>
)
}
handleChange(e) {
this.setState({
username: e.target.value
})
}
}
export default App
HOC
高阶组件的英文是 Higher-Order Components,简称为 HOC
高阶组件本质上就是一个函数,但该函数需要满足如下两点:
- 接收一个组件作为参数
- 返回一个新的组件
所以高阶组件可以对我们传入的组件,进行拦截,进行增强处理后再返回
所以高阶组件也是react中代码抽离并复用的一种方式
高阶组件并不是React API的一部分,它是基于React的组合特性而形成的设计模式
HOC的意义
早期的React有提供组件之间的一种复用方式是mixin,目前已经不再建议使用
- Mixin 可能会相互依赖,相互耦合,不利于代码维护
- 不同的Mixin中的方法可能会相互冲突
- Mixin非常多时,组件处理起来会比较麻烦,甚至还要为其做相关处理,这样会给代码造成滚雪球式的复杂性
所以React提供了HOC来实现代码的复用
但是HOC也存在如下缺点:
- HOC需要在原组件上进行包裹或者嵌套,如果大量使用HOC,将会产生非常多的嵌套,这让调试变得非常困难
- HOC可以劫持props,在不遵守约定的情况下也可能造成冲突
所以在实际开发中,在不使用react hook的情况下,推荐使用HOC来抽离公用代码,
但是如果使用react hook,推荐使用react hook 来替代 HOC,从而实现对应的功能
注入props
with-props.jsx
import { PureComponent } from 'react';
export default function enchangePropsFn(OriginCpn) {
return class enhancedCpn extends PureComponent {
constructor(props) {
super(props)
this.state = {
// 在这里定义一些需要注入的数据
userInfo: {
name: 'Klaus',
age: 23
}
}
}
render() {
// 在进行props注入的时候,也需要将原本传入的props一起传递进去
return <OriginCpn {...this.props} {...this.state.userInfo} />
}
}
}
使用
import React, { PureComponent } from 'react'
import withProps from '../../../utils/with-props'
class Foo extends PureComponent {
render() {
return (
<div>Foo: {this.props.name} - {this.props.age}</div>
)
}
}
export default withProps(Foo)
import React, { memo } from 'react'
import withProps from '../../../utils/with-props'
const Home = memo(props => {
return (
<div>Home - {props.name} - {props.age}</div>
)
})
export default withProps(Home)
共享context
import { ThemeContext } from '../context/themeContext'
export default function enhance(OriginCpn) {
return function enhanceCpn(props) {
return (
<ThemeContext.Consumer>
{
value => <OriginCpn {...value} {...props} />
}
</ThemeContext.Consumer>
)
}
}
portals
通常来讲,当执行组件的 render 方法返回一个元素时,该元素将被挂载到 DOM 节点中离其最近的父节点
而某些情况下,我们希望渲染的内容独立于父组件,甚至是独立于当前挂载到的DOM元素中(默认都是挂载到id为root的DOM 元素上的)
如在实现弹出框的时候,一般推荐将弹出框挂载到body下边,而不是离其最近的父节点下边
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案:
- 第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment
- 第二个参数(container)是一个 DOM 元素
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>React App</title>
</head>
<body>
<div id="root"></div>
<div id="app"></div>
<div id="modal"></div>
</body>
</html>
App.jsx
import React, { PureComponent } from 'react'
// createPortal 是在 react-dom 中导出的
import { createPortal } from 'react-dom'
export class index extends PureComponent {
render() {
return (
<div>
<div>something in div.root</div>
{
// createPortal本质上也是一个高阶组件
// createPortal接收两个参数
// 参数一 => 需要挂载的元素内容
// => 只要是JSX中支持挂载的内容,都可以作为该方法的第一个参数被传入
// 参数二 => 需要挂载的新的挂载点 - DOM节点
createPortal(
<div>something in div.app</div>,
document.getElementById('app')
)
}
{
// createPortal方法的第一个参数可以是children数组
createPortal(
(
<div>
<div>something in div.modal</div>
<div>other thing in div.modal</div>
</div>
),
document.getElementById('modal')
)
}
</div>
)
}
}
export default index
fragment
在之前的开发中,我们总是在一个组件中返回内容时包裹一个div元素
如果希望可以不渲染这样一个div, 可以使用fragment
Fragment 允许你将子列表分组,而无需向 DOM 添加额外节点
React还提供了Fragment的短语法, 它看起来像空标签 <> </>
但是,如果我们需要在Fragment中添加key,那么就不能使用短语法
import React, { Fragment, PureComponent } from 'react'
export class App extends PureComponent {
render() {
return (
<Fragment>
App
</Fragment>
)
}
}
export default App
import React, { PureComponent } from 'react'
export class App extends PureComponent {
render() {
// 使用短语法的时候,可以不引入Fragment
// 在编译的时候,会自动引入Fragment
return (
<>
App
</>
)
}
}
export default App
StrictMode
StrictMode 是一个用来突出显示应用程序中潜在问题的工具
与 Fragment 一样,StrictMode 不会渲染任何可见的 UI
它为其后代元素触发额外的检查和警告
严格模式检查仅在开发模式下运行;它们不会影响生产构建
import { StrictMode } from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
// 全局开启严格模式
ReactDOM.createRoot(document.querySelector('#root')).render((
<StrictMode>
<App />
</StrictMode>
))
import React, { Fragment, PureComponent, StrictMode } from 'react'
import Foo from './components/Foo'
import Home from './components/Home'
export class App extends PureComponent {
render() {
return (
<Fragment>
{/* 局部开启严格模式 */}
<StrictMode>
<Home />
</StrictMode>
<Foo />
</Fragment>
)
}
}
export default App
作用
- 识别不安全的生命周期
- 使用过时的ref API
- 检查意外的副作用
- 这个组件的constructor会被调用两次
- 这是严格模式下故意进行的操作,让你来查看在这里写的一些逻辑代码被调用多次时,是否会产生一些副作用
- 在生产环境中,是不会被调用两次的
- 使用废弃的findDOMNode方法
- 在之前的React API中,可以通过findDOMNode来获取DOM,不过已经不推荐使用了
转载自:https://juejin.cn/post/7140008840052867108