二、React面向组件编程
大约 10 分钟约 2946 字
(一)基本理解和使用
1.使用 React 开发者工具调试
- 基于 Chrome 浏览器:React Developer Tools
- 提供方:Facebook / Meta 公司
2.函数式组件
- 执行了 ReactDOM.render(...)之后,发生了什么?
- React 解析组件标签,找到了 MyComponent 组件
- 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟 DOM 转为真实 DOM,随后呈现在页面中
- 此处的 this 是 undefined,babel 编译后开启了严格模式,禁止自定义函数内的 this 指向 window
<script type="text/babel">
{
/* 1.创建函数式组件 */
}
function MyComponent() {
console.log(this);
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>;
}
{
/* 2.渲染组件到页面 */
}
ReactDOM.render(<MyComponent />, document.getElementById("test"));
</script>
3.类式组件
- 执行了 ReactDOM.render(...)之后,发生了什么?
- React 解析组件标签,找到了 MyComponent 组件
- 发现组件是使用类定义的,随后 new 出来该类的实例,并通过该实例调用到原型上的 render 方法
- 将 render 返回的虚拟 DOM 转为真实 DOM,随后呈现在页面中
- render 是放在类的原型对象上,供实例使用
- render 中的 this 是 MyComponent 的实例对象 / MyComponent 组件实例对象
<script type="text/babel">
{
/* 1.创建类式组件 */
}
class MyComponent extends React.Component {
render() {
console.log("render中的this:", this);
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>;
}
}
{
/* 2.渲染组件到页面 */
}
ReactDOM.render(<MyComponent />, document.getElementById("test"));
</script>
4.注意
- 组件名必须首字母大写
- 虚拟 DOM 元素只能有一个根元素
- 虚拟 DOM 元素必须有结束标签
5.渲染类组件标签的基本流程
- React 内部会创建组件实例对象
- 调用
render()
得到虚拟 DOM,并解析为真实 DOM - 插入到指定的页面元素内部
(二)组件三大核心属性 1:state
1.理解
state
是组件对象最重要的属性,值是对象- 可以包含多个
key-value
的组合
- 可以包含多个
- 组件被称为“状态机”,通过更新组件的
state
来更新对应的页面显示- 重新渲染组件
2.强烈注意
- 组件中
render
方法中的this
是组件实例对象 - 组件自定义的方法中
this
为undefined
,如何解决?- 强制绑定
this
:通过函数对象的bind()
- 使用类中赋值语句+箭头函数的形式
- 强制绑定
- 状态数据,不能直接修改或更新,需要使用
this.setState()
API
class MyComponent extends React.component {
// 强制绑定this,不推荐,无端引入构造器
/*
constructor(props) {
super(props);
this.changeWeather = this.changeWeather.bind(this);
}
*/
// 初始化状态
state = {
isHot: true,
};
// 自定义方法
changeWeather = () => {
// 修改状态数据
this.setState({
isHot: !isHot,
});
};
render() {
return <h2>今天天气很 {isHot ? "炎热" : "凉爽"} </h2>;
}
}
(三)组件三大核心属性 2:props
1.理解
- 每个组件对象都会有
props
(properties 的简写)属性 - 组件标签的所有属性都保存在
props
中
2.作用
- 通过标签属性从 组件外 向 组件内 传递变化的数据
- 注意:组件内部不要修改
props
数据
3.编码操作
- 内部读取某个属性值:
this.props.name
- 对
props
中的属性值进行类型限制和必要性限制
1)方式一
- React v15.5 开始已弃用
Person.propTypes = {
name: React.PropTypes.string.isRequired,
age: React.Prototypes.number,
};
2)方式二
- 使用
prop-types
库进行限制,需要引入prop-types
库
Person.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
};
- 扩展属性:将对象的所有属性通过
props
传递
<Person {...person} />
- 默认属性值
Person.defaultProps = {
age: 18,
sex: "男",
};
- 组件类的构造函数
constructor(props) {
super(props);
console.log(props); // 打印所有属性
}
(四)组件三大核心属性 3:refs 与事件处理
1.理解
- 组件内的标签可以定义
ref
属性来标识自己
2.字符串形式
<input ref="input1" />
3.回调形式
<input ref={(c) => (this.input1 = c)}
createRef
创建 ref
容器
4.myRef = React.creatRef();
render() {
<input ref={this.myRef} />
}
5.事件处理
- 通过
onXxxx
属性指定事件处理函数(注意大小写)- onclick =>
onClick
, onblur =>onBlur
, ...... - React 使用的是自定义(合成)事件,而不是使用的原生 DOM 事件 —— 为了更好的兼容性
- React 中的事件是通过事件委托方式处理的(委托给组件最外层的元素) —— 为了更好的高效性
- onclick =>
- 通过
event.target
得到发生事件的 DOM 元素对象- 勿过度使用
ref
- 当发生事件的元素正好是要操作的元素时,事件回调可以收到
event
参数
- 勿过度使用
// 展示右侧输入框的数据
showData2 = (event) => {
// alert(this.myRef2.current.value);
alert(event.target.value);
}
render() {
return <input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据" />
}
(五)收集表单数据
1.受控组件【推荐】
- 将表单中的输入类 DOM 数据通过监听
onChange
事件保存到状态中,需要用时再从状态中读取
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(); // 阻止表单提交
console.log(this.state);
const { username, password } = this.state;
alert(`你输入的用户名是:${username},你输入的密码是:${password}`);
};
render() {
return (
// <form action="http://www.atguigu.com" onSubmit={this.handleSubmit}>
<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.非受控组件
- 表单中所有输入类 DOM 的值都是现用现取的
class Login extends React.Component {
handleSubmit = (event) => {
event.preventDefault(); // 阻止表单提交
const { username, password } = this;
alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`);
};
render() {
return (
<form action="http://www.atguigu.com" 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>
);
}
}
(六)高阶函数-函数柯里化
1.高阶函数
- 如果一个函数符合下面 2 个规范中的 任何一个 ,那该函数就是高阶函数
- 若 A 函数,接收的参数是一个函数,那么 A 就可以称为高阶函数
- 若 A 函数,调用的返回值依然是一个函数,那么 A 就可以称为高阶函数
- 常见的高阶函数:
- Promise
- setTimeout、setInterval
- arr.map()
- .....
2.函数柯里化
- 通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
class Login extends React.Component {
// 初始化状态
state = {
username: "",
password: "",
};
// 保存表单数据到状态中
/*
// 该函数返回值是undefined
saveFormData = (event) => {
console.log(event.target.value);
this.setState({ username: event.target.value });
}
*/
// 该函数返回值是一个高阶函数,返回的函数才是onChange事件的回调
saveFormData = (dataType) => {
return (event) => {
console.log(dataType, event.target.value);
// 以下两行代码等价,相当于给状态增加了'dataType'字段,应该使用[dataType]获取属性值
// this.setState({ dataType: event.target.value });
// this.setState({ 'dataType': event.target.value });
this.setState({ [dataType]: event.target.value });
};
};
render() {
return (
<form>
{/*
以下写法是将this.saveFormData('xxx')的返回值作为onChange事件的回调,不是将函数本身作为事件回调,
所以事件只触发一次且是页面初始化之后立即触发
<input onChange={this.saveFormData('username')} type="text" name="username" />
以下写法等价,都是保证onChange事件的回调是一个函数
<input onChange={this.saveFormData('username')} type="text" name="username" />
<input onChange={this.saveFormData('username')()()} type="text" name="username" />
*/}
账号:
<input onChange={this.saveFormData("username")} type="text" name="username" />
<br />
密码:
<input onChange={this.saveFormData("password")} type="password" name="password" />
<br />
<button>登录</button>
</form>
);
}
}
(七)组件的生命周期
1.理解
- 生命周期回调函数 / 生命周期钩子函数 / 生命周期函数 / 生命周期钩子
- 组件从创建到死亡会经历一些特定的阶段
- React 组件中包含一系列钩子函数(生命周期回调函数),会在特定的时刻调用
- 在定义组件时,会在特定的生命周期回调函数中做特定的工作
2.生命周期(旧)
- 生命周期的三个阶段(旧):
ReactDOM.render()
触发
1)初始化阶段(初次渲染):由 constructor()
componentWillMount()
render()
componentDidMount()
【常用】- 一般在这个钩子中做一些初始化的事
- 例如:开启定时器、发送网络请求、订阅消息
this.setState()
触发或父组件重新 render
渲染
2)更新阶段:由组件内部 shouldComponentUpdate()
componentWillUpdate()
render()
【必须】componentDidUpdate()
ReactDOM.unmountComponentAtNode()
触发
3)卸载组件:由 componentWillUnmount()
【常用】- 一般在这个钩子中做一些收尾的事
- 例如:关闭定时器、取消订阅消息
3.生命周期(新)
- 生命周期的三个阶段(新):
ReactDOM.render()
触发
1)初始化阶段(初次渲染):由 constructor()
getDerivedStateFromProps()
- 当
state
的值在任何时候都取决于props
时使用 - 必须声明为
static
- 返回
null
时不作任何处理 - 返回
state object
时会覆盖state
中已定义的同名数据,且后续不可更新
- 当
render()
componentDidMount()
【常用】- 一般在这个钩子中做一些初始化的事
- 例如:开启定时器、发送网络请求、订阅消息
this.setState()
触发或父组件重新 render
渲染
2)更新阶段:由组件内部 getDerivedStateFromProps()
shouldComponentUpdate()
render()
【必须】getSnapshotBeforeUpdate()
- 在更新之前获取快照
- 在最近一次渲染输出(提交到 DOM 节点)之前调用
- 当需要在组件发生更改之前从 DOM 中捕获一些信息时用
- 如滚动位置
- 返回值将作为参数传递给
componentDidUpdate()
componentDidUpdate()
ReactDOM.unmountComponentAtNode()
触发
3)卸载组件:由 componentWillUnmount()
【常用】- 一般在这个钩子中做一些收尾的事
- 例如:关闭定时器、取消订阅消息
4.重要的钩子
render()
:初始化渲染或更新渲染调用componentDidMount()
:开启监听,发送ajax
请求componentWillUnmount()
:做一些收尾工作,如:清理定时器
5.即将废弃的钩子
componentWillMount()
componentWillReceiveProps()
componentWillUpdate()
相关信息
16 版本使用会出现警告,17 版本需要加上 UNSAFE_
前缀才能使用,18 版本废弃
(八)虚拟 DOM 与 DOM Diffing 算法
1.经典面试题
react/vue
中的key
有什么作用?(key
的内部原理是什么?)- 为什么遍历列表时,
key
最好不要用index
?
2.虚拟 DOM 中 key 的作用
1)简单地说
key
是虚拟 DOM 对象的标识,在更新显示时key
起着极其重要的作用
2)详细地说
- 当状态中的数据发生改变时,
React
会根据【新数据】生成【新的虚拟 DOM】,随后 React 进行【新虚拟 DOM】与【旧虚拟 DOM】的diff
比较,比较规则如下:- 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的
key
:- 若虚拟 DOM 中内容没变,直接使用之前的真实 DOM
- 若虚拟 DOM 中内容变了,则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM
- 旧虚拟 DOM 中未找到与新虚拟 DOM 相同的
key
:- 根据数据创建新的真实 DOM,随后渲染到页面
- 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的
index
作为 key
可能会引发的问题
3.用 - 若对数据进行【逆序添加】、【逆序删除】等破坏顺序的操作
- 会产生没有必要的真实 DOM 更新
- 界面效果没问题,但是效率低
- 若结构中还包含输入类的 DOM
- 会产生错误的 DOM 更新
- 界面效果有问题
注意
如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表,使用 index
作为 key
是没有问题的
key
?
4.开发中如何选择 - 最好使用每条数据的唯一标识作为
key
- 比如 id、手机号、身份证号、学号等唯一值
- 如果确定只是简单的展示数据,用
index
也是可以的