day-073-seventy-three-20230519-React类组件的特点及受控组件与非受控组件-TodoList使用Antd框架及React组件绑定事件
React类组件的特点及受控组件与非受控组件
类组件的特点
-
类组件
是动态组件
,它拥有属性props
、上下文
、状态state
、钩子函数
、refs
等内容。-
在组件内部,单纯地改动组件实例对象的状态,并不能像vue一样改变视图。
import { Component } from "react"; class DemoTwo extends Component { /* 属性规则校验 */ static defaultProps = { x: 0, y: false, }; /* 初始化状态 */ state = { num: this.props.x, //10 count: 0, }; render() { console.log("render"); let { title } = this.props, { num, count } = this.state; return ( <div className="box"> {title} && {num} && {count} <br /> <button onClick={() => { this.state.num++; //这样操作,仅仅是立即修改了状态值,但是视图不会更新 console.log(this.state.num); }} > 修改NUM </button> <br /> <button>修改COUNT</button> </div> ); } } export default DemoTwo;
- 这样操作,仅仅是立即修改了状态值,但是视图不会更新。
-
在组件内部,可以基于
修改状态
、强制更新
、父组件重新调用此组件
等方式,让组件更新。-
而
类组件的更新
,不是重新创建一个新的实例
,而是把实例上的render()方法
重新执行,用最新的数据
,渲染出新的VirtualDOM虚拟DOM
,再经过DOM-diff
,进行差异化更新
。渲染
–狭义来说一般指的是第一次
把VirtualDOM虚拟DOM
渲染进页面。更新
–狭义来说一般指的是非第一次
重新调用render()方法
重新更新页面。
-
通过
setState()方法
修改状态,可以控制组件更新页面。<button onClick={() => { this.setState({ num: num + 1 }, () => { console.log(`setState触发的回调函数callback`,this.state); }); }} > 修改num </button>
import { Component } from "react"; import PT from "prop-types"; class DemoTwo extends Component { /* 属性规则校验 */ static defaultProps = { x: 0, y: false, }; static propTypes = { title: PT.string.isRequired, x: PT.oneOfType([PT.number, PT.string]), y: PT.bool, }; /* 初始化状态 */ state = { num: this.props.x, //10; count: 0, // }; render() { let { title } = this.props; let { num, count } = this.state; console.log(this); return ( <div className="box"> title:{title} -- num:{num} --count:{count} <br /> <button onClick={() => { /* this.state.num++;//这样操作,仅仅是立即修改了状态值,但是视图不会更新。 console.log(this.state.num); */ // 只有基于特定的方法setState修改状态,才可以保证,状态更新的同时,视图也会立即更新。 }} > 修改num </button> <br /> <button>修改count</button> </div> ); } } export default DemoTwo;
-
触发安全的
shouldComponentUpdate()
钩子函数。-
shouldComponentUpdate()
表示是否允许更新。- 方法返回
true
,说明允许更新,则进行下一步的操作。 - 如果返回
false
,则是禁止其更新,那么所有的操作到此结束。- 但是依旧会把
setState(dataObject,callback)
中的callback()
执行。
- 但是依旧会把
- 如果不写该
shouldComponentUpdate()
函数,默认返回true
。
- 方法返回
-
在执行这个钩子函数的时候,
状态值
还没有改变。-
或者说还没有修改
this实例上的状态
和this实例上的属性
。shouldComponentUpdate(nextProps, nextState) { // nextProps & nextState:即将要修改的最新的属性和状态。 // this.props & this.state :原有的属性和状态。 //return true return false //方法返回true,说明允许更新,则进行下一步的操作。如果返回false,则是禁止其更新,那么所有的操作到此结束。 }
-
在
shouldComponentUpdate(nextProps, nextState)
中:nextProps
:即将要修改的最新的属性
;nextState
:即将要修改的最新的状态
;this.props
:原有的属性
;this.state
:原有的状态
;
-
-
真实项目中,一般在此钩子函数中进行优化!
- 比如说:
- 改了一些状态,但不想让它更新,就返回false。
- 因为如果返回true,那么就React就会产生VirtualDOM虚拟DOM,之后进行比对,比较消耗性能。
- 改了一些状态,但不想让它更新,就返回false。
- 比如说:
-
-
触发不安全的
componentWillUpdate()
钩子函数。-
触发时期:更新之前。
-
此钩子函数是不安全的,不建议使用。
- 可以忽略它,改用
shouldComponentUpdate()
。
- 可以忽略它,改用
-
如果不写该函数,默认返回true。
-
执行此钩子函数的时候,
实例上的状态值
还是没有更改的。UNSAFE_componentWillUpdate(nextProps, nextState) { console.log( `componentWillUpdate(nextProps, nextState)`, nextState, this.state ); return true; }
componentWillUpdate(nextProps, nextState) { // nextProps & nextState:即将要修改的最新的属性和状态。 // this.props & this.state :原有的属性和状态。 // ... return true //方法返回true,说明允许更新,则进行下一步的操作。 //return false//如果返回false,则是禁止其更新,那么所有的操作到此结束。 }
-
-
内部开始把
实例上的状态state
与实例上的属性props
进行更改。 -
触发
render()
钩子函数。- 组件开始更新。
- 按照最新的数据,把所有视图重新生成
新的VirtualDOM虚拟DOM
,然后和之前的虚拟DOM
进行对比,最后把差异的部分进行更新渲染。
- 按照最新的数据,把所有视图重新生成
- 组件开始更新。
-
触发安全的
componentDidUpdate()
钩子函数。-
组件更新完毕。
componentDidUpdate(){ // ... }
-
-
如果执行
setState(修改的状态,callback)
时传递了callback函数
,则在DOM更新完毕
后,会把callback函数
触发执行!- 如果是不论那个状态改变,在改变后都要执行的事件,我们写在
componentDidUpdate()
中! - 但是如果是指定的状态改变,才会去做的事件,我们一般写在
this.setState(修改的状态,callback)
对应的callback函数
中!
- 如果是不论那个状态改变,在改变后都要执行的事件,我们写在
-
Vue可以通过
get()
与set()
劫持,在修改数据时,通知视图执行。其实也类似于React的this.setState()
。import { Component } from "react"; class DemoTwo extends Component { /* 属性规则校验 */ static defaultProps = { x: 0, y: false, }; /* 初始化状态 */ state = { num: this.props.x, //10 count: 0, }; render() { console.log("render"); let { title } = this.props, { num, count } = this.state; return ( <div className="box"> {title} && {num} && {count} <br /> <button onClick={() => { // 只有基于特点的方法 setState 修改状态,才可以保证,状态更新的同时,视图也会更新 // Component.prototype.setState = function (partialState, callback) {...} // setState是支持部分状态更改的 this.setState( { num: num + 1, }, () => { console.log("callback"); } ); }} > 修改NUM </button> <br /> <button>修改COUNT</button> </div> ); } } export default DemoTwo;
-
-
通过forceUpdate()让视图强制更新。
Component.prototype.forceUpdate = function (callback) {...}
<button onClick={() => { // 强制更新 this.state.num++ this.forceUpdate(()=>{ console.log(`forceUpdate触发的回调函数callback`,this.state); }) }} > 修改num </button>
- 触发不安全的
componentWillUpdate()
钩子函数。 - 触发
render()
钩子函数。 - 触发安全的
componentDidUpdate()
钩子函数。 - 把
this.forceUpdate()
中传递的callback函数
执行。- 真实项目中,一般很少使用
forceUpdate()
。- 因为其太暴力了,会忽略在
shouldComponentUpdat()
中做的优化项!
- 因为其太暴力了,会忽略在
- 真实项目中,一般很少使用
-
此方法通知视图更新,会直接跳过
shouldComponentUpdate()
钩子函数。import { Component } from "react"; class DemoTwo extends Component { /* 属性规则校验 */ static defaultProps = { x: 0, y: false, }; /* 初始化状态 */ state = { num: this.props.x, //10 count: 0, }; render() { console.log("render"); let { title } = this.props, { num, count } = this.state; return ( <div className="box"> {title} && {num} && {count} <br /> <button onClick={() => { // 强制更新 this.state.num++; this.forceUpdate(() => { console.log("forceUpdate callback"); }); }} > 修改NUM </button> <br /> <button>修改COUNT</button> </div> ); } } export default DemoTwo;
- 触发不安全的
-
当父组件
重新调用
子组件。- 或者说
父组件更新
,那么它所涉及的子组件
也会跟着更新
! 重新调用子组件
就可以传递最新的属性值
进来!- 触发
不安全
的componentWillReceiveProps(nextProps)
钩子函数。- 在
接收新的属性值props之前
。 - 此时
this.props中的值
还没变,nextProps
存储的是最新要修改的属性props
!
- 在
- 触发安全的
shouldComponentUpdate()
钩子函数。 - 触发不安全的
componentWillUpdate()
钩子函数。 - 内部开始把
实例上的状态state
与实例上的属性props
进行更改。 - 触发
render()
钩子函数。 - 触发安全的
componentDidUpdate()
钩子函数。
- 触发
- 也就是说平常
当前组件更新
,那么当前组件的所有子组件
也会跟着更新
!
- 或者说
-
import { Component } from "react"; import PT from "prop-types"; class DemoTwo extends Component { // 属性规则校验 static defaultProps = { x: 0, y: false, }; static propTypes = { title: PT.string.isRequired, x: PT.oneOfType([PT.number, PT.string]), y: PT.bool, }; // 初始化状态 state = { num: this.props.x, //10 count: 0, }; render() { console.log("render"); let { title } = this.props, { num, count } = this.state; return ( <div className="box"> {title} && {num} && {count} <br /> <button onClick={() => { // 只有基于特点的方法 setState 修改状态,才可以保证,状态更新的同时,视图也会更新 // Component.prototype.setState = function (partialState, callback) {...} // setState是支持部分状态更改的 this.setState( { num: num + 1, }, () => { console.log("this.setState()回调函数callback();"); } ); // // 强制更新 // this.state.num++; // this.forceUpdate(() => { // console.log("forceUpdate callback"); // }); }} > 修改NUM </button> <br /> <button>修改COUNT</button> </div> ); } // 父组件props更新。 componentWillReceiveProps(nextProps) { console.log(`componentWillReceiveProps()父组件props更新。`, nextProps); return true; } // 子组件setState(),视图应该更新时。 shouldComponentUpdate(nextProps, nextState) { console.log("shouldComponentUpdate视图应该更新时。", nextState, this.state); return true; } // 视图将要更新。 UNSAFE_componentWillUpdate() { console.log("componentWillUpdate视图将要更新。", this.state); return true; } // 视图更新后。 componentDidUpdate() { console.log("componentDidUpdate视图更新后。"); } } export default DemoTwo;
-
-
组件总有一天会被
销毁
与卸载
。- 例如:
- 每次路由跳转,默认都是
上一个组件
卸载,新组件
开始渲染。新组件
走第一次渲染逻辑
。
当前组件的父组件
被销毁
。- …
- 每次路由跳转,默认都是
- 触发
componentWillUnmount()
钩子函数。- 触发时期:
组件卸载之前
。 - 真实项目中,可以在卸载之前做的一些事情:
- 消除手动设置的定时器、监听器。
- 信息草稿箱。
- 如保存一些数据,下一次重新渲染时,复用这些数据。
- 弹出弹框提示用户当前组件中的数据可能会被丢失。
- …
- 触发时期:
import { Component } from "react"; class DemoTwo extends Component { render() { return ( <div className="box"> <br /> <button>修改COUNT</button> </div> ); } // 组件将要销毁前。 componentWillUnmount() { console.log("componentWillUnmount()组件将要销毁前。"); return true; } } export default DemoTwo;
- 例如:
受控组件与非受控组件
-
受控组件:由数据/状态来管控视图的渲染。
// 受控组件:由数据/状态来管控视图的渲染 class DemoThree extends Component { state = { show: true, }; render() { let { show } = this.state; return ( <div className="box"> <button onClick={() => { this.setState({ show: !show, }); }} > 开关 </button> <br /> <div className="detail" style={{ display: show ? "block" : "none", }} > 我是一个详细信息 </div> </div> ); } } export default DemoThree;
-
组件内部使用数据与状态来管控视图的渲染。
// 受控组件-由数据与状态来管控视图的渲染。 class RefsDemo extends Component { state = { show: true, }; render() { let { show } = this.state; return ( <div className="box"> <button onClick={() => { this.setState({ show: !show }); }} > toggle </button> <br /> <div className="detail" style={{ display: show ? `block` : `none`, }} > this a detail详细信息 </div> </div> ); } }
-
-
非受控组件
-
不受状态管控,想要修改视图就需要直接操作DOM–在React中是不推荐的。
// 非受控组件--不受状态管控,直接操作DOM--在React中是不推荐的。 // 我们需要获取DOM元素。 // 契机:只有等待组件第一次渲染完毕后,我们才可以获取到真实DOM。 class RefsDemo extends Component { state = { show: true, }; render() { return ( <div className="box"> <button>toggle</button> <br /> <div className="detail">this a detail详细信息</div> </div> ); } }
-
由于组件视图中不受状态管控,想要修改视图就只能直接操作DOM。
-
假设我们需要获取DOM元素。
- 契机:只有等待组件第一次渲染完毕后,我们才可以获取到真实DOM。
-
契机例子:
- 在onClick等事件中。
- 在钩子函数中。
- …
-
可以基于传统获取DOM的方式如通过id来操作。
- 这个方法在React中理论上是可以的,但实际操作起来,不建议使用。并且实际上很少有人使用。
-
在React/Vue框架中,都有提供好的获取DOM元素的方式 -->
ref="ref名"
;-
很旧的方案:给ref属性设置为一个字符串。
<div ref="AAA">...</div>
this.refs.AAA
-> DOM元素
render() { return ( <div className="box"> <button onClick={() => { console.log(this.refs, this.refs.AAA); }} > toggle </button> <br /> <div className="detail" ref="AAA"> this a detail详细信息 </div> </div> ); }
- 但是这种方式官方已经不推荐了,在React.StrictMode模式下,控制台会报错
Warning: A string ref
。
-
给ref设置的属性值是一个函数。
- 比较推荐的方法。
<div ref={x => this.AAA = x}>
;this.AAA
->DOM元素
;
class RefsDemo extends Component { state = { show: true, }; render() { return ( <div className="box"> <button onClick={() => { console.log(this.AAA); }} > toggle </button> <br /> <div className="detail" ref={(x) => (this.AAA = x)}> this a detail详细信息 </div> </div> ); } }
- 比较推荐的方法。
-
基于官方推荐的
React.createRef()
(类组件
与函数组件
)与React.useRef()
(函数组件)方法去处理。- 基于
React.createRef()
与React.useRef()
创建一个ref对象
:{current:null}
; - 让
元素的ref属性值
,等于创建的ref对象
。 - 最后基于
ref对象.current属性
,获取真实的DOM
。
import { createRef } from "react"; class RefsDemo extends Component { state = { show: true, }; AAA = createRef(); //this.AAA = {current:null} render() { return ( <div className="box"> <button onClick={() => { console.log(this.AAA.current); }} > toggle </button> <br /> <div className="detail" ref={this.AAA}> this a detail详细信息 </div> </div> ); } }
```jsx class DemoThree extends Component { AAA = createRef(); // this.AAA = { current: null } render() { return ( <div className="box"> <button onClick={() => { console.log(this.AAA.current); }} > 开关 </button> <br /> <div className="detail" ref={this.AAA}> 我是一个详细信息 </div> </div> ); } } export default DemoThree; ```
- 基于
-
-
ref除了可以获取视图中的真实DOM元素,还可以获取子组件的实例。
-
ref绑定一个元素标签,目的是获取这个真实的DOM。
-
ref绑定一个组件,目的:
- 一般是获取
组件的实例
。 - 或者是获取
组件内部的某些元素
。 - 再或者是获取
组件暴露出来的属性和方法
。
- 一般是获取
-
给类组件绑定ref,最后获取的是
类型为类组件的子组件实例对象
。class DetailBox extends Component { state = {x:100} render() { return <div className="detail">this a detail详细信息 --- {this.state.x}</div>; } } class RefsDemo extends Component { render() { return ( <div className="box"> <button onClick={() => { console.log(this.AAA); }} > toggle </button> <br /> <DetailBox ref={(x) => (this.AAA = x)}>this a detail详细信息</DetailBox> </div> ); } }
-
当
子组件的实例
都获取到了后,那么在子组件的父组件
即当前组件
中再想获取子组件中的什么信息
,直接基于该子组件实例
处理即可。import React, { Component, createRef } from "react"; class DetailBox extends Component { state = { x: 100 }; detailBox = createRef(); render() { return ( <div className="detail" ref={this.detailBox}> 我是一个详细信息 {this.state.x} </div> ); } } class DemoThree extends Component { render() { return ( <div className="box"> <button onClick={() => { console.log(this.AAA); }} > 开关 </button> <DetailBox ref={(x) => (this.AAA = x)} /> </div> ); } } export default DemoThree;
- 所有在子组件实例上挂载的东西,我们都可以基于
this.AAA.xxx
获取。-
如果需要获取子组件内部的元素:
- 在子组件中,基于ref把对应的元素,挂载到子组件的实例上。
- 这样父组件,就可以直接基于子组件实例去获取。
import { createRef } from "react"; class DetailBox extends Component { state = { x: 100 }; detailBox = createRef(); render() { return ( <div className="detail" ref={this.detailBox}> this a detail详细信息 --- {this.state.x} </div> ); } } class RefsDemo extends Component { render() { return ( <div className="box"> <button onClick={() => { console.log(this.AAA); console.log(this.AAA.state.x); console.log(this.AAA.detailBox); }} > toggle </button> <br /> <DetailBox ref={(x) => (this.AAA = x)}></DetailBox> </div> ); } }
-
- 所有在子组件实例上挂载的东西,我们都可以基于
-
-
如果是给函数组件,绑定ref,则直接报错
Warning: Function components cannot be given refs.
。function DetailBox() { return <div className="detail">我是一个详细信息</div>; } class RefsDemo extends Component { render() { return ( <div className="box"> <DetailBox ref={(x) => (this.AAA = x)}></DetailBox> </div> ); } }
- 因为直接绑定是不被允许的。
-
但是我们是有方法去解决的!
import React, { Component, createRef } from "react"; function DetailBox() { return <div className="detail">我是一个详细信息</div>; } class DemoThree extends Component { render() { return ( <div className="box"> <button onClick={() => { console.log(this.AAA); }} > 开关 </button> <DetailBox ref={(x) => (this.AAA = x)} /> </div> ); } } export default DemoThree;
-
- 因为直接绑定是不被允许的。
-
-
- 契机:只有等待组件第一次渲染完毕后,我们才可以获取到真实DOM。
-
TodoList使用Antd框架及React组件绑定事件
- React框架体系的UI组件库。
- Atnd。
- AntdMobile。
Antd框架
- 使用Antd这个UI组件库(v5.0及之后)。
-
自动实现了按需导入。
- 但是v5之前的版本,自动按需导入可能不生效,需要我们手动的处理一下。
-
安装babel-plugin-import这个配置。
yarn add babel-plugin-import
-
配置
/config/webpack.config.js
或/package.json
其中一个关于babel-plugin-import这个babel插件
中的配置。
-
/package.json
{ "babel": { "presets": [...], "plugins": [ [ "import", { "libraryName": "antd", "style": true } ] ] } }
-
- 但是v5之前的版本,自动按需导入可能不生效,需要我们手动的处理一下。
-
我们在使用之前,需要进行国际化处理。
- ant.design-安装及上手
-
默认组件库中的方案都是英文的,我们需要让其变为中文。
-
从antd引入ConfigProvider这个组件和中文语言包。
import { ConfigProvider } from 'antd' import zhCN from 'antd/locale/zh_CN'
-
把需要转为中文的包含antd组件的组件都包裹在设置了locale这个props为中文语言包zhCN的ConfigProvider组件中。
<ConfigProvider locale={zhCN}> //其它组件... </ConfigProvider>
import { ConfigProvider } from 'antd' import zhCN from 'antd/locale/zh_CN' root.render( <ConfigProvider locale={zhCN}> //... </ConfigProvider> )
import React from "react"; import ReactDOM from "react-dom/client"; // antd的汉化。 import { ConfigProvider } from 'antd'; import zhCN from 'antd/locale/zh_CN'; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <ConfigProvider locale={zhCN}> //其它组件。需要在<ConfigProvider>这个包起来。 </ConfigProvider> );
-
-
但日期类组件,其内部的方案还没有汉化。
-
V5版本开始,日期类组件,其内部日期的处理,采用的是day.js,原来是moment.js插件。
import dayjs from "dayjs"; import "dayjs/locale/zh-cn"; dayjs.locale("zh-cn");
-
-
- ant.design-安装及上手
React组件绑定事件
- 直接调用定义在原型上的方法-不行。
- 用bind绑定this之后执行放到原型上的方法。
- 直接调用定义在实例上的箭头函数方法。
- 这个是推荐的。
用bind实现预先传值
- 基于bind实现预先传值。
- 对于箭头函数,this是谁不重要。但可以用bind传递一些默认参数。