React 开发者工具
推荐使用Chrome或Edge浏览器,安装React Developer Tools
(Facebook出品)。
安装完成后,访问使用React编写的页面时,图标会高亮(开发环境为红色有debug标识,生产环境为蓝色),同时F12开发者工具中会多出Components
和Profiler
两个选项卡。
React组件
函数式组件
效果
简单组件:无状态state
代码
// 1.创建函数式组件
function Demo() { // 创建组件,函数名首字母须大写
console.log(this) // 此处的this是undefined,因为babel编译后开启了严格模式
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
// 2.渲染组件到页面
ReactDOM.render(<Demo/>, document.getElementById('test'))
执行
ReactDOM.render(<Demo/>, document.getElementById('test'))
后发生了什么?
React解析组件标签,找到了Demo组件
发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中
类式组件
JavaScript类
构造
// 创建一个Person类 class Person { // 构造器方法 constructor(name, age) { // 构造器中的this:类的实例对象 this.name = name this.age = age } // 一般方法 speak() { // speak()方法放在类的原型对象上,供实例使用 // 通过Person实例调用speak时,speak中的this就是Person实例 // 谁调用它,它指向谁 console.log(`我叫${this.name},我的年龄是${this.age}`) } } // 创建一个Person的实例对象 const p1 = new Person('tom', 20) const p2 = new Person('jerry', 22) console.log(p1) console.log(p2) p1.speak() p2.speak()
继承
// 创建一个Student类,继承于Person类 class Student extends Person { // 无新增属性可不写构造器方法 constructor(name, age, grade) { super(name, age) this.grade = grade this.school = 'xxxx' } // 重写从父类继承的方法 speak() { console.log(`我叫${this.name},我的年龄是${this.age},我今年读${this.grade}`) } study() { // study()方法放在类的原型对象上,供实例使用 // 通过Student实例调用study时,study中的this就是Student实例 console.log('我每天努力学习') } } // 创建一个Student的实例对象 const s1 = new Student('小王', 15) const s2 = new Student('小张', 16, '高一') console.log(s1) console.log(s2) s2.speak()
- 类中的构造器不是必须写的,要对实例进行一些初始化的操作(如添加指定属性)时才写;
- 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的;
- 类中所定义的方法,都是放在了类的原型对象上,供实例去使用
类中可以直接写赋值语句。
类中this的指向
class Person { constructor(name, age) { this.name = name this.age = age } speak() { console.log(this) } } const p1 = new Person('tom', 18) p1.speak() const x = p1.speak x()
效果
复杂组件:有状态state
代码
// 1.创建类式组件
class Demo extends React.Component {
render() {
// render方法放在Demo的原型对象上,供实例使用
// render中的this:Demo的实例对象
console.log('render中的this: ', this)
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
}
}
// 2.渲染组件到页面
ReactDOM.render(<Demo/>, document.getElementById('test'))
执行
ReactDOM.render(<Demo/>, document.getElementById('test'))
后发生了什么?
- React解析组件标签,找到了Demo组件
- 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法
- 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中
简单组件与复杂组件的区别
简单组件:无状态 state
复杂组件:有状态 state
组件实例的三大核心属性
1.state
原生js中的事件绑定
<body> <button id="btn1">按钮1</button> <button id="btn2">按钮2</button> <button onclick="demo()">按钮3</button> <script> const btn1 = document.getElementById('btn1') btn1.addEventListener('click', ()=>{ alert('点击按钮1') }) // 兼容性最好 const btn2 = document.getElementById('btn2') btn2.onclick = ()=>{ alert('点击按钮2') } function demo() { alert('点击按钮3') } </script> </body>
React中的事件绑定
class Weather extends React.Component { constructor(props) { super(props) // 初始化状态 this.state = {isHot: true} } render() { console.log(this) // 读取状态 const {isHot} = this.state return <h1 onClick={demo}>今天天气很{isHot ? '炎热' : '凉爽'}</h1> } } function demo() { console.log('标题被点击') }
注意
return <h1 onClick={demo}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
中html用法要改为驼峰,demo
函数不需要加括号
类内的方法作为回调时,不通过实例调用
class Weather extends React.Component {
constructor(props) {
super(props)
// 初始化状态
this.state = {isHot: true}
}
render() {
// console.log(this)
// 读取状态
const {isHot} = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
}
changeWeather() {
// changeWeather放在Weather的原型对象上,供实例使用
// 由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
// 类中的方法默认开启了局部的严格模式,所以changeWeather中的this是undefined
console.log(this)
}
}
解决方法:在构造器中绑定
this.changeWeather = this.changeWeather.bind(this)
JavaScript中的bind:
function demo() { console.log(this) } demo() const x = demo.bind({a:1, b:'b'}) x()
效果
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>三大属性 - state</title>
</head>
<body>
<div id="test"></div>
<!-- 引入 React 核心库 -->
<script type="text/javascript" src="../../js/react.development.js"></script>
<!-- 引入 react-dom -->
<script type="text/javascript" src="../../js/react-dom.development.js"></script>
<!-- 引入babel 将jsx转为js -->
<script type="text/javascript" src="../../js/babel.min.js"></script>
<script type="text/babel"> /* 此处必须为babel */
// 1.创建组件
class Weather extends React.Component {
// 构造器调用——1次
constructor(props) {
super(props)
// 初始化状态
this.state = {isHot: true}
// 绑定changeWeather中的this指向
this.changeWeather = this.changeWeather.bind(this)
}
// render调用——1+n次(初始化+状态更新次数)
render() {
// console.log(this)
// 读取状态
const {isHot} = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
}
// changeWeather调用次数与点击次数相同
changeWeather() {
// changeWeather放在Weather的原型对象上,供实例使用
// 由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
// 类中的方法默认开启了局部的严格模式,所以changeWeather中的this是undefined
// 获取原来的isHot值
const isHot = this.state.isHot
// !!! 状态(state)不可直接更改,要借助一个内置的API去更改
// 状态必须通过setState进行更新,且更新是一种合并,不是替换,即:未涉及更新的参数不会丢失
this.setState({isHot: !isHot})
// 不可取: this.state.isHot = !isHot
}
}
// 2.渲染组件到页面
ReactDOM.render(<Weather/>, document.getElementById('test'))
</script>
</body>
</html>
state的简写方式
class Weather extends React.Component {
// 初始化状态
state = {isHot: true}
render() {
const {isHot} = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
}
// 自定义方法——赋值语句+箭头函数
changeWeather = ()=>{
const isHot = this.state.isHot
this.setState({isHot: !isHot})
}
}
理解
- state是组件对象最重要的属性,值是对象(可以包含多个
key-value
的组合) - 组件被称为“状态机”,通过更新组件的state来更新对应的页面显示(重新渲染组件)
2.props
props是只读的
class Test extends React.Component {
render() {
console.log(this)
return <div></div>
}
}
ReactDOM.render(<Test key1="value1" key2="value2" />, document.getElementById('test'))
批量传递props
class Person extends React.Component {
render() {
console.log(this)
const {name, age, sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
)
}
}
const p = {name: 'Tom', age: 18, sex: '男'}
ReactDOM.render(<Person {...p} />, document.getElementById('test1'))
ReactDOM.render(<Person name='Amy' age={} sex='女' />, document.getElementById('test2'))
展开运算符
let arr1 = [1, 3, 5, 7, 9] let arr2 = [2, 4, 6, 8, 10] console.log(arr1) console.log(...arr1) // 展开一个数组 let arr3 = [...arr1, ...arr2] // 连接两个数组 console.log(arr3) function sum(...numbers) { // 数组传参 return numbers.reduce((preValue, currentValue) => { return preValue + currentValue }) } console.log(sum(1, 2, 3, 4, 5)) let person1 = { name: 'tom', age: 18 } let person2 = { ...person1 } // 构造字面量对象 person1.name = 'jerry' console.log('person2 :>> ', person2); let person3 = { ...person1, name: 'jack', address: 'where' } // 复制对象并修改 console.log('person3 :>> ', person3);
对props进行限制
原因:如果不对props加以限制,其他输入数据的人无法得知props需要的数据类型,例如ReactDOM.render(<Person name='Amy' age='19' sex='女' />, document.getElementById('test2'))
与ReactDOM.render(<Person name='Amy' age={19} sex='女' />, document.getElementById('test2'))
。
预期效果:
- 姓名必须指定,且为字符串类型;
- 性别为字符串类型,如果性别没有指定,默认为男
- 年龄为字符串类型,且为数字类型,默认值为18
限制方法:
-
引入依赖包
<script type="text/javascript" src="../../js/prop-types.js"></script>
-
限制标签属性:
Person.propTypes = { name: PropTypes.string.isRequired, // 限制name必传,为字符串 sex: PropTypes.string, // 限制sex为字符串 age: PropTypes.number, // 限制age为数值 speak: PropTypes.func // 限制speak为函数 } Person.defaultProps = { sex: '男', age: 18 }
-
效果:
ReactDOM.render(<Person name={120} />, document.getElementById('test2'))
props的简写方式
class Person extends React.Component {
// 进行限制
static propTypes = {
name: PropTypes.string.isRequired, // 限制name必传,为字符串
sex: PropTypes.string, // 限制sex为字符串
age: PropTypes.number, // 限制age为数值
speak: PropTypes.func // 限制speak为函数
}
static defaultProps = {
sex: '男',
age: 18
}
render() {
/* console.log(this) */
const {name, age, sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age+1}</li>
<li>性别:{sex}</li>
</ul>
)
}
}
构造器与props
constructor(props) {
super(props)
console.log('constructor',this.props);
}
构造器是否接收props,是否传递给super,取决于是否希望在构造器中通过this访问props,极其罕见
如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。
通常,在 React 中,构造函数仅用于以下两种情况:
- 通过给
this.state
赋值对象来初始化内部 state。- 为事件处理函数绑定实例
函数式组件使用props
function Person(props) {
console.log(props);
const {name, age, sex} = props
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
)
}
ReactDOM.render(<Person name='Jack' age={20} sex='男' />, document.getElementById('test3'))
添加限制:
Person.propTypes = {
name: PropTypes.string.isRequired, // 限制name必传,为字符串
sex: PropTypes.string, // 限制sex为字符串
age: PropTypes.number, // 限制age为数值
}
Person.defaultProps = {
sex: '男',
age: 18
}
ReactDOM.render(<Person name={100}/>, document.getElementById('test3'))
理解
- 每个组件对象都会有props(properties的简写)属性
- 组件标签的所有属性都保存在props中
作用:
- 通过标签属性从组件外向组件内传递变化的数据
- 注意: 组件内部不要修改props数据
3.refs
refs收集多组ref
class Demo extends React.Component {
showThis = () => {
console.log(this)
}
render() {
return (
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据"/>
<button ref="button" onClick={this.showThis}>点击提示左侧输入框数据</button>
<input ref="input2" type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
效果
字符串形式的ref - 不太推荐
// 创建组件
class Demo extends React.Component {
// 展示左侧输入框数据
showLeft = () => {
const {input1} = this.refs
alert(input1.value)
}
// 展示右侧输入框数据
showRight = () => {
const {input2} = this.refs
alert(input2.value)
}
render() {
return (
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showLeft}>点击提示左侧输入框数据</button>
<input ref="input2" onBlur={this.showRight} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
// 渲染组件到页面
ReactDOM.render(<Demo />, document.getElementById('test'))
过时 API:String 类型的 Refs
如果你之前使用过 React,你可能了解过之前的 API 中的 string 类型的 ref 属性,例如
"textInput"
。你可以通过this.refs.textInput
来访问 DOM 节点。我们不建议使用它,因为 string 类型的 refs 存在 一些问题。它已过时并可能会在未来的版本被移除。
回调函数形式的ref
class Demo extends React.Component {
// 展示左侧输入框数据
showLeft = () => {
const {input1} = this
alert(input1.value)
}
// 展示右侧输入框数据
showRight = () => {
const {input2} = this
alert(input2.value)
}
render() {
return (
<div>
<input ref={(c) => {this.input1 = c}} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showLeft}>点击提示左侧输入框数据</button>
<input ref={c => this.input2 = c} onBlur={this.showRight} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
回调ref中的调用次数:
- 首次渲染:调用1次
- 页面更新:调用2次
关于回调 refs 的说明
如果
ref
回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数null
,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
class Demo extends React.Component {
state = {isHot: true}
showInfo = () => {
const {input1} = this
alert(input1.value)
}
changeWeather = () => {
const {isHot} = this.state
this.setState({isHot: !isHot})
}
render() {
const {isHot} = this.state
return (
<div>
<h2>今天天气很{isHot ? '炎热' : '凉爽'}</h2>
<input ref={(c) => {this.input1 = c; console.log('@', c)}} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showInfo}>点击提示左侧输入框数据</button>
<button onClick={this.changeWeather}>点击修改天气</button>
</div>
)
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U9lwpWEj-1672296614662)(null)]
避免多次回调:将 ref 的回调函数定义成 class 的绑定函数
saveInput = (c) => {
this.input1 = c;
console.log('@', c)
}
render() {
const {isHot} = this.state
return (
<div>
<h2>今天天气很{isHot ? '炎热' : '凉爽'}</h2>
<input ref={this.saveInput} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showInfo}>点击提示左侧输入框数据</button>
<button onClick={this.changeWeather}>点击修改天气</button>
</div>
)
}
createRef API
官方最推荐的创建ref的形式
class Demo extends React.Component {
/*
React.createRef调用后可以返回一个容器,该容器可以存储被ref标识的节点,该容器是“专人专用”的
*/
myRefLeft = React.createRef()
myRefRight = React.createRef()
// 展示左侧输入框数据
showLeft = () => {
alert(this.myRefLeft.current.value)
}
// 展示右侧输入框数据
showRight = () => {
alert(this.myRefRight.current.value)
}
render() {
return (
<div>
<input ref={this.myRefLeft} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showLeft}>点击提示左侧输入框数据</button>
<input ref={this.myRefRight} onBlur={this.showRight} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
事件处理
-
通过onXxx属性指定事件处理函数(注意大小写)
(1)React使用的是自定义(合成)事件, 而不是使用的原生DOM事件——为了更好的兼容性
(2)React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)——高效
-
通过event.target得到发生事件的DOM元素对象——不要过度使用ref
收集表单数据
效果:
非受控组件
页面内输入类DOM(input、checkbox、radio等)都是现用现取
class Login extends React.Component {
handleSubmit = (event) => {
event.preventDefault() // 阻止默认事件——组织表单提交
const {username, password} = this
alert(`你输入的用户名是${username.value},密码是${password.value}`)
}
render() {
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>
)
}
}
受控组件
随着输入直接维护到状态中,需要时直接从状态中取得(类比:vue的双向数据绑定)
class Login extends React.Component {
// 初始化状态
state = {username: '', password: ''}
// 保存用户名和密码到状态中
saveUsername = (event) => {
this.setState({username:event.target.value})
}
savePassword = (event) => {
this.setState({password:event.target.value})
}
// 表单提交的回调
handleSubmit = (event) => {
event.preventDefault() // 阻止默认事件——组织表单提交
const {username, password} = this.state
alert(`你输入的用户名是${username},密码是${password}`)
}
render() {
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>
)
}
}
高阶函数与函数柯里化
// 保存表单数据到状态中
saveFormData = (dataType) => {
return ((event) => {
this.setState({[dataType]:event.target.value})
})
}
render() {
return (
<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>
)
}
js方括号
let a = 'name' let obj = {} let obj1 = {} obj[a] = 'tom' obj1.a = 'tommy' console.log(obj) console.log(obj1)
高阶函数
如果一个函数符合下面两个规范中的任何一个,那该函数就是高阶函数
- A函数接收的参数是一个函数
- A函数调用的返回值是一个函数
常见的高阶函数:Promise
、setTimeout
、arr.map()
等
函数的柯里化
通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
/*function sum(a, b, c) {
return a+b+c
}
result = sum(1, 2, 3)*/
function sum(a) {
return (b) => {
return (c) => {
return a+b+c
}
}
}
result = sum(1)(2)(3)
console.log(result);
不用柯里化的写法
// 不用柯里化
saveFormDataN = (dataType, event) => {
this.setState({[dataType]:event.target.value})
}
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={(event)=>{this.saveFormDataN('username', event)}} type="text" name="username" /><br/>
密码:<input onChange={(event)=>{this.saveFormDataN('password', event)}} type="password" name="password" /><br/>
<button>登录</button>
</form>
)
}
组件的生命周期
理解生命周期
生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子
// 创建组件
// 生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子
class Demo extends React.Component {
state = { opacity: 1 }
death = () => {
// 卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 组件挂载完毕调用
componentDidMount() {
this.timer = setInterval(() => {
// 获取原状态
let { opacity } = this.state
// -0.1
opacity -= 0.1
if (opacity <= 0) opacity = 1
// 设置新状态
this.setState({ opacity })
}, 200)
}
// 组件将要卸载调用
componentWillUnmount() {
clearInterval(this.timer)
}
// 初始化渲染、状态更新调用
render() {
console.log('render');
return (
<div>
<h2 style={{ opacity: this.state.opacity }}>React学不会了可咋办捏</h2>
<button onClick={this.death}>不活了</button>
</div>
)
}
}
// 渲染组件到页面
ReactDOM.render(<Demo />, document.getElementById('test'))
- 组件从创建到死亡它会经历一些特定的阶段。
- React组件中包含一系列钩子函数(生命周期回调函数), 会在特定的时刻调用。
- 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。
旧版生命周期
流程图
setState()与forceUpdate()流程
// 创建组件
class Count extends React.Component {
// 构造器
constructor(props) {
console.log('Count - constructor')
super(props)
// 初始化状态
this.state = {count: 0}
}
// +1按钮的回调
add = () => {
// 获取原状态
const {count} = this.state
// 更新状态
this.setState({count: count+1})
}
// 卸载组件按钮的回调
death = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 强制更新按钮的回调
force = () => {
this.forceUpdate()
}
// 组件将要挂载的钩子
componentWillMount() {
console.log('Count - componentWillMount');
}
// 组件挂载完毕的钩子
componentDidMount() {
console.log('Count - componentDidMount');
}
// 组件将要卸载的钩子
componentWillUnmount() {
console.log('Count - componentWillUnmount');
}
// 控制组件更新的“阀门”
shouldComponentUpdate() {
console.log('Count - shouldComponentUpdate');
return true // 重写shouldComponentUpdate()方法,必须有返回值
}
// 组件将要更新的钩子
componentWillUpdate() {
console.log('Count - componentWillUpdate');
}
// 组件更新完毕的钩子
componentDidUpdate() {
console.log('Count - componentDidUpdate');
}
render() {
console.log('Count - render');
const {count} = this.state
return (
<div>
<h2>当前计数为{count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不更改状态强制更新组件</button>
</div>
)
}
}
// 渲染组件到页面
ReactDOM.render(<Count />, document.getElementById('test'))
父组件render流程
// 创建组件
class A extends React.Component {
state = {carName: '奔驰'}
change = () => {
this.setState({carName: '特斯拉'})
}
render() {
return (
<div>
<h2>我是A组件</h2>
<button onClick={this.change}>换车</button>
<B carName={this.state.carName}/>
</div>
)
}
}
class B extends React.Component {
// 组件将要接收新的props的钩子
componentWillReceiveProps(props) { // 坑:第一次调用的不算
console.log('B - componentWillReceiveProps', props);
}
// 控制组件更新的“阀门”
shouldComponentUpdate() {
console.log('B - shouldComponentUpdate');
return true // 重写shouldComponentUpdate()方法,必须有返回值
}
// 组件将要更新的钩子
componentWillUpdate() {
console.log('B - componentWillUpdate');
}
// 组件更新完毕的钩子
componentDidUpdate() {
console.log('B - componentDidUpdate');
}
render() {
console.log('B - render');
return(
<div>
<h2>我是B组件,我的车是{this.props.carName}</h2>
</div>
)
}
}
// 渲染组件到页面
ReactDOM.render(<A />, document.getElementById('test'))
总结旧版生命周期的三个阶段
1.初始化阶段: 由ReactDOM.render()
触发—初次渲染
constructor()
componentWillMount()
render()
componentDidMount()
====> 常用
一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
2.更新阶段: 由组件内部this.setState()
或父组件重新render()
触发
shouldComponentUpdate()
componentWillUpdate()
render()
====> 必须componentDidUpdate()
3.卸载组件: 由ReactDOM.unmountComponentAtNode()
触发
componentWillUnmount()
====> 常用
一般在这个钩子中做收尾的事,例如:关闭定时器、取消订阅
新版生命周期
更新js文件,升级为17.0.1版本
<!-- 引入 React 核心库 -->
<script type="text/javascript" src="../js/17.0.1/react.development.js"></script>
<!-- 引入 react-dom -->
<script type="text/javascript" src="../js/17.0.1/react-dom.development.js"></script>
<!-- 引入babel 将jsx转为js -->
<script type="text/javascript" src="../js/17.0.1/babel.min.js"></script>
流程图
getDerivedStateFromProps()
getDerivedStateFromProps
会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回null
则不更新任何内容。此方法适用于罕见的用例,即 state 的值在任何时候都取决于 props。例如,实现
<Transition>
组件可能很方便,该组件会比较当前组件与下一组件,以决定针对哪些组件进行转场动画。
class Count extends React.Component {
...
static getDerivedStateFromProps(props, state) {
console.log('getDerivedStateFromProps', props, state);
return props
}
...
}
ReactDOM.render(<Count count={568}/>, document.getElementById('test'))
getSnapshotBeforeUpdate()
getSnapshotBeforeUpdate()
在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给componentDidUpdate()
。此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。
应返回 snapshot 的值(或
null
)。
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="newsList" ref="list">
{this.state.newsArr.map((n, index) => {
return <div key={index} className="news">{n}</div>
})}
</div>
)
}
}
ReactDOM.render(<NewsList/>, document.getElementById('test'))
总结新版生命周期的三个阶段
1.初始化阶段: 由ReactDOM.render()
触发—初次渲染
constructor()
getDerivedStateFromProps
render()
componentDidMount()
====> 常用
一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
2.更新阶段: 由组件内部this.setSate()
或父组件重新render触发
getDerivedStateFromProps
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate
componentDidUpdate()
3.卸载组件: 由ReactDOM.unmountComponentAtNode()
触发
componentWillUnmount()
====> 常用
一般在这个钩子中做收尾的事,例如:关闭定时器、取消订阅
重要的钩子
render
:初始化渲染或更新渲染调用componentDidMount
:开启监听, 发送ajax请求componentWillUnmount
:做一些收尾工作, 如: 清理定时器
即将废弃的钩子
componentWillMount
componentWillReceiveProps
componentWillUpdate
现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。
DOM的Diffing算法
当对比两棵树时,React 首先比较两棵树的根节点。不同类型的根节点元素会有不同的形态。
Diffing最小的粒度是标签。
“粒度”表示的是精确程度问题。粗粒度角度描述一个系统,是关注系统中大的组件;细粒度角度描述一个系统是从组成大组件的小组件,或者更小组件的角度认识系统。
系统功能一般又分为多个模块,大的功能又会分为若干模块或者步骤,粒度一步一步细化,直到最终的某个用户操作(输入内容,下拉选择,上传文件,点击按钮等),具体的功能最终得到实现。这是一个粒度由粗到细的过程。反之就是由细到粗。
再从代码设计的角度来说。代码的结构是由类型之间的关联起来的系统。系统的整体结构(架构)就是系统最粗的粒度,代码也同系统功能一样,也是有模块划分(可能类似功能结构划分,也可能有所区别)。那么从复杂结构代码模块,到其细小的组成部分就是粒度由粗到细的过程。
验证Diffing算法
class Time extends React.Component {
state = {date: new Date()}
componentDidMount() {
setInterval(() => {
this.setState({date: new Date()})
}, 1000)
}
render() {
return (
<div>
<h1>Hello</h1>
<input type="text"/>
<span>
现在是:{this.state.date.toTimeString()}
<input type="text"/>
</span>
</div>
)
}
}
ReactDOM.render(<Time/>, document.getElementById('test'))
两个输入框中的输入内容均没有丢失,证明Diffing算法是真实存在的
虚拟DOM中key的作用
简单地说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用
详细的说:当状态中的数据发生变化时,react会根据新数据生成新的虚拟DOM,随后react进行新虚拟DOM与旧虚拟DOM的diff比较,规则如下:
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 若虚拟DOM中内容未变,直接使用之前的真实DOM
- 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
- 旧虚拟DOM中未找到与新虚拟DOM相同的key:
- 根据数据创建新的真实DOM,随后渲染到页面
用index作为key可能会引发的问题
- 若对数据进行:逆序添加、逆序删除等破坏顺序的操作:
- 会产生没有必要的真实DOM更新 ===> 界面效果没问题,但效率低
- 如果结构中包含输入类的DOM:
- 会产生错误DOM更新 ===> 界面有问题
注意:如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key没有问题
class Person extends React.Component {
state = {
persons:[
{id:1, name:'小张', age:18},
{id:2, name:'小王', age:20}
]
}
add = () => {
const {persons} = this.state
const p = {id:persons.length+1, name:'小李', age:35}
this.setState({persons: [p, ...persons]})
}
render() {
return(
<div>
<h2>展示人员信息</h2>
<h3>使用index(索引值)作为key</h3>
<button onClick={this.add}>添加一个小李</button>
<ul>
{
this.state.persons.map((personObj, index) => {
return <li key={index}>{personObj.name}----{personObj.age}<input type="text"/></li>
})
}
</ul>
<hr/>
<h3>使用obj.id(数据的唯一标识)作为key</h3>
<ul>
{
this.state.persons.map((personObj) => {
return <li key={personObj.id}>{personObj.name}----{personObj.age}<input type="text"/></li>
})
}
</ul>
</div>
)
}
}
ReactDOM.render(<Person/>, document.getElementById('test'))
开发中如何选择key
- 最好使用每条数据的唯一标识作为key,比如id、手机号、学号、身份证号等
- 如果确定只是简单的展示数据,使用index亦可
on extends React.Component {
state = {
persons:[
{id:1, name:'小张', age:18},
{id:2, name:'小王', age:20}
]
}
add = () => {
const {persons} = this.state
const p = {id:persons.length+1, name:'小李', age:35}
this.setState({persons: [p, ...persons]})
}
render() {
return(
<div>
<h2>展示人员信息</h2>
<h3>使用index(索引值)作为key</h3>
<button onClick={this.add}>添加一个小李</button>
<ul>
{
this.state.persons.map((personObj, index) => {
return <li key={index}>{personObj.name}----{personObj.age}<input type="text"/></li>
})
}
</ul>
<hr/>
<h3>使用obj.id(数据的唯一标识)作为key</h3>
<ul>
{
this.state.persons.map((personObj) => {
return <li key={personObj.id}>{personObj.name}----{personObj.age}<input type="text"/></li>
})
}
</ul>
</div>
)
}
}
ReactDOM.render(<Person/>, document.getElementById('test'))