前言
上一篇:React面向组件编程(上)
本篇内容:React面向组件编程(下)
一、非受控组件与受控组件
收集表单数据:
需求:定义一个包含表单的组件,输入用户名密码后, 点击登录弹出输入的用户名及密码
1.非受控组件
现用现取: 表单中所有输入类DOM(input框、单选、多选等)
class Login extends React.Component{
handleSubmit=(event)=>{
event.preventDefault(); // 阻止默认事件————表单提交
const {username,password}=this;
alert(`你输入的用户名是${username.value},你输入的密码是${password.value}`);
}
render(){
// { /* <form action="http://www.atguigu.com" onSubmit={this.handleSubmit}> */ }
return(
<form onSubmit={this.handleSubmit}>
用户名:<input ref={c => this.username=c} type="text" name="username"/><br/>
密码:<input ref={c => this.password=c} type="password" name="password"/><br/>
<button>登录</button>
</form>
)
}
}
2.受控组件
-
随着输入来维护状态,想到Vue中的双向数据绑定
-
推荐使用受控组件,因为它可以省略掉ref
class Login extends React.Component{
// 初始化状态
state={
username:'',
password:''
}
// 保存用户名到状态中
saveUsername=(event)=>{
// console.log(event.target.value);
this.setState({username:event.target.value});
}
// 保存密码到状态中
savePassword=(event)=>{
// console.log(event.target.value);
this.setState({password:event.target.value});
}
// 表单提交的回调
handleSubmit=(event)=>{
event.preventDefault(); // 阻止默认事件————表单提交
const {username,password}=this.state;
alert(`你输入的用户名是${username},你输入的密码是${password}`);
}
render(){
// { /* <form action="http://www.atguigu.com" onSubmit={this.handleSubmit}> */ }
return(
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUsername} type="text" name="username"/><br/>
密码:<input onChange={this.savePassword} type="password" name="password"/><br/>
<button>登录</button>
</form>
)
}
}
二、高阶函数与函数柯里化
高阶函数:
如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数
1. 函数接收的参数是一个函数
2. 函数调用的返回值依然是一个函数
常见的高阶函数有:Promise、setTimeout、arr.map()等
函数的柯里化: 通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
函数柯里化示例:
function sum(a){
return(b)=>{
return(c)=>{
return a+b+c;
}
}
}
const result=sum(1)(2)(3);
当我们要提交的数据不止用户名密码时,比如还有姓名、年龄、地址、电话等等,我们需要写很多个函数来提交信息,很臃肿。而利用函数柯里化,我们写一个函数就行。
class Login extends React.Component{
// 初始化状态
state={
username:'',
password:''
}
// 保存表单数据到状态中
saveFormData=(dataType)=>{ // 此处saveFormData就是高阶函数
//console.log('##',dataType);
return (event)=>{ // 此处的return才是onChange的回调
//console.log(dataType,event.target.value);
this.setState({[dataType]:event.target.value});
}
}
// 表单提交的回调
handleSubmit=(event)=>{
event.preventDefault(); // 阻止默认事件————表单提交
const {username,password}=this.state;
alert(`你输入的用户名是${username},你输入的密码是${password}`);
}
render(){
return(
// onChange={this.saveFormData('uaername')} 把saveFormData()的返回值作为onChange的回调,而其返回值是undefined
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveFormData('username')} type="text" name="username"/><br/>
密码:<input onChange={this.saveFormData('password')} type="password" name="password"/><br/>
<button>登录</button>
</form>
)
}
}
三、不用柯里化的写法
用户名:<input onChange={event => this.saveFormData('username',event)} type="text" name="username"/><br/>
密码:<input onChange={event => this.saveFormData('password',event)} type="password" name="password"/><br/>
// 保存表单数据到状态中
saveFormData=(dataType,event)=>{
this.setState({[dataType]:event.target.value});
}
四、组件的生命周期
1.引出生命周期
需求:定义组件实现以下功能:
- 让指定的文本做显示 / 隐藏的渐变动画
- 从完全可见,到彻底消失,耗时2S
- 点击“不活了”按钮从界面中卸载组件
class Life extends React.Component{
// 页面有更新 状态中的数据驱动着页面显示
state={opacity:1}
death=()=>{
// // 清除定时器
// clearInterval(this.timer);
// 卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'));
}
// 组件挂载完毕 (只执行一次)
componentDidMount(){
this.timer = setInterval(()=>{
let {opacity}=this.state;
opacity-=0.1;
if(opacity<=0) opacity=1;
// 设置新的透明度
this.setState({opacity})
},200)
}
// 组件将要卸载
componentWillUnmount(){
// 清除定时器
clearInterval(this.timer);
}
// render调用时机:初始化渲染、状态更新之后
// 若把定时器放在render中,会造成死循环 cpu温度飙升 render调用次数指数式上升
render(){
return(
<div>
<h2 style={{opacity:this.state.opacity}}>React学不会怎么办?</h2>
<button onClick={this.death}>不活了</button>
</div>
)
}
}
生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子
2.生命周期(旧)_组件挂载流程
-->红色框中是组件挂载流程class Add extends React.Component{
constructor(props){
console.log('Add---constructor');
super(props);
// 初始化状态
this.state={count:0};
}
// 加1按钮的回调
add=()=>{
// 获取原状态
const {count}=this.state;
// 更新状态
this.setState({count:count+1});
}
//组件将要挂载的钩子
componentWillMount(){
console.log('Add---componentWillMount');
}
// 组件挂载完毕的钩子
componentDidMount(){
console.log('Add---componentDidMount');
}
render(){
console.log('Add---render');
const {count}=this.state;
return (
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>点我加1</button>
</div>
)
}
}
控制台输出顺序:
3.生命周期(旧)_setState流程
正常更新过程
// 控制组件更新的“阀门”
// 若不写,则底层会自动补,且默认返回值为true
// 若写了,则必须有返回值,否则有警告
shouldComponentUpdate(){
console.log('Add---shouldComponentUpdate');
return true;
}
// 组件将要更新的钩子
componentWillUpdate(){
console.log('Add---componentWillUpdate');
}
// 组件更新完毕的钩子
componentDidUpdate(){
console.log('Add---componentDidUpdate');
}
4.生命周期(旧)_froceUpdate流程
强制更新:没对状态作出任何的更改,但想更新
<button onClick={this.force}>不更改状态中任何数据,强制更新一下</button>
// 强制更新按钮的回调
force=()=>{
this.forceUpdate();
}
5.生命周期(旧)_父组件render流程
父组件—A
class A extends React.Component{
// 初始化状态
state={carName:'奔驰'}
changeCar=()=>{
this.setState({carName:'宝马'})
}
render(){
return(
<div>
<div>我是A组件</div>
<button onClick={this.changeCar}>换车</button>
<B carName={this.state.carName}/>
</div>
)
}
}
子组件—B
class B extends React.Component{
// 组件将要接收新的props的钩子 (只有父组件再次render时才会调用)
componentWillReceiveProps(props){
console.log('B---componentWillReceiveProps',props);
}
// 控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('B---shouldComponentUpdate');
return true;
}
// 组件将要更新的钩子
componentWillUpdate(){
console.log('B---componentWillUpdate');
}
// 组件更新完毕的钩子
componentDidUpdate(){
console.log('B---componentDidUpdate');
}
render(){
console.log('B---render');
return(
<div>我是B组件,接收到的车是:{this.props.carName}</div>
)
}
}
6.总结生命周期(旧)
-
初始化阶段: 由ReactDOM.render()触发—初次渲染
1.constructor()
2.componentWillMount()
3.render()
4.componentDidMount() === > 常用。一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息 -
更新阶段: 由组件内部this.setSate()或父组件render触发
1.shouldComponentUpdate()
2.componentWillUpdate()
3.render() === > 必须使用的
4.componentDidUpdate() -
卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1.componentWillUnmount()===> 常用。一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
7.对比新旧生命周期
之前的依赖包版本均为16.8.4
下载新版本的依赖包:React官网–>文档–>CDN链接–>打开你想下载的那个文件的src,ctrl+s保存即可
推荐网站:BootCDN,前端人员一些常用的js库的线上地址
新生命周期图👇
与旧生命周期相比:
即将废弃三个钩子(componentWillMount、componentWillUpdate、componentWillReceiveProps),新增两个钩子(getDerivedStateFromProps、getSnapshotBeforeUpdate)
8.getDerivedStateFromProps
了解即可
官网上的介绍👇
ReactDOM.render(<Add count={199}/>,document.getElementById('test'));
static getDerivedStateFromProps(props,state){
console.log('Add---getDerivedStateFromProps',props,state);
return props
}
9.getSnapshotBeforeUpdate
官网上的介绍👇
// 在更新之前获取快照
getSnapshotBeforeUpdate(){
console.log('Add---getSnapshotBeforeUpdate');
return 'atguigu'
}
// 组件更新完毕的钩子
componentDidUpdate(preProps,preState,snapshotValue){
console.log('Add---componentDidUpdate',preProps,preState,snapshotValue);
}
10.getSnapshotBeforeUpdate举例
class NewsList extends React.Component{
state = {newsArr:[]}
componentDidMount(){
setInterval(()=>{
// 获取原状态
const {newsArr}=this.state
// 模拟一条新闻
const news='新闻'+ (newsArr.length+1);
// 更新状态
this.setState({newsArr:[news,...newsArr]});
},1000);
}
getSnapshotBeforeUpdate(){
// 内容区现在的高度
return this.refs.list.scrollHeight;
}
componentDidUpdate(preProps,preState,height){
// 新的高度减去上一步传来的高度
this.refs.list.scrollTop+=this.refs.list.scrollHeight-height;
}
render(){
return (
<div className="list" ref="list">
{
this.state.newsArr.map((n,index)=>{
return <div key={index} className="news">{n}</div>
})
}
</div>
)
}
}
11.总结生命周期(新)
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
1.constructor()
2.getDerivedStateFromProps
3.render()
4.componentDidMount() ===> 常用。一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
1.getDerivedStateFromProps
2.shouldComponentUpdate()
3.render()
4.getSnapshotBeforeUpdate
5.componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1.componentWillUnmount() ===> 常用。一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
五、DOM的diffing算法
1.验证diffing算法
class Time extends React.Component{
state={time:new Date()}
componentDidMount(){
setInterval(()=>{
this.setState({
time:new Date()
})
},1000)
}
render(){
// input框中的输入值不会消失,diffing算法:最小粒度是标签(节点)
return (
<div>
<h2>hello diffing</h2>
<input type="text"/>
<span>现在是:{this.state.time.toTimeString()}
<input type="text"/>
</span>
</div>
)
}
}
input框中的值不会发生变化:
2.key的作用
class Person extends React.Component{
state={persons:[
{id:1,name:'小李',age:18},
{id:2,name:'小王',age:19},
]
}
add=()=>{
const {persons}=this.state;
const p={id:persons.length+1,name:'小赵',age:20};
this.setState({persons:[p,...persons]});
}
render(){
return (
<div>
<h2>展示人员信息</h2>
<button onClick={this.add}>添加一个小赵</button>
<h3>使用index(索引值)作为key</h3>
<ul>
{
this.state.persons.map((personObj,index)=>{
return <li key={index}>{personObj.name}---{personObj.age}<input type="text"/></li>
})
}
</ul>
<hr/>
<hr/>
<h3>使用id(数据的唯一标识)作为key</h3>
<ul>
{
this.state.persons.map((personObj)=>{
return <li key={personObj.id}>{personObj.name}---{personObj.age}<input type="text"/></li>
})
}
</ul>
</div>
)
}
}
- 虚拟DOM中key的作用:
当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
(2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随后渲染到到页面 - 用index作为key可能会引发的问题:
(1)若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 = => 界面效果没问题, 但效率低。
(2)如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题。
(3)注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。 - 开发中如何选择key?
(1)最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
(2)如果确定只是简单的展示数据,用index也是可以的。
写在后面
一周了,从上周日到现在,看完了三分之一的视频。