React 开发者工具
推荐使用Chrome或Edge浏览器,安装React Developer Tools
(Facebook出品)。
安装完成后,访问使用React编写的页面时,图标会高亮(开发环境为红色有debug标识,生产环境为蓝色),同时F12开发者工具中会多出Components
和Profiler
两个选项卡。
React组件
函数式组件
效果
简单组件:无状态state
代码
1 2 3 4 5 6 7
| function Demo() { console.log(this) return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2> }
ReactDOM.render(<Demo/>, document.getElementById('test'))
|
执行ReactDOM.render(<Demo/>, document.getElementById('test'))
后发生了什么?
-
React解析组件标签,找到了Demo组件
-
发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中
类式组件
JavaScript类
构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Person {
constructor(name, age) {
this.name = name this.age = age }
speak() {
console.log(`我叫${this.name},我的年龄是${this.age}`) } }
const p1 = new Person('tom', 20) const p2 = new Person('jerry', 22)
console.log(p1) console.log(p2)
p1.speak() p2.speak()
|
继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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() {
console.log('我每天努力学习') } }
const s1 = new Student('小王', 15) const s2 = new Student('小张', 16, '高一')
console.log(s1) console.log(s2)
s2.speak()
|
- 类中的构造器不是必须写的,要对实例进行一些初始化的操作(如添加指定属性)时才写;
- 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的;
- 类中所定义的方法,都是放在了类的原型对象上,供实例去使用
类中可以直接写赋值语句。
类中this的指向
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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 2 3 4 5 6 7 8 9 10 11
| class Demo extends React.Component { render() { console.log('render中的this: ', this) return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2> } }
ReactDOM.render(<Demo/>, document.getElementById('test'))
|
执行ReactDOM.render(<Demo/>, document.getElementById('test'))
后发生了什么?
- React解析组件标签,找到了Demo组件
- 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法
- 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中
简单组件与复杂组件的区别
简单组件:无状态 state
复杂组件:有状态 state
组件实例的三大核心属性
1.state
原生js中的事件绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <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中的事件绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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
函数不需要加括号
类内的方法作为回调时,不通过实例调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Weather extends React.Component { constructor(props) { super(props) this.state = {isHot: true} } render() { const {isHot} = this.state return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h1> } changeWeather() { console.log(this) } }
|
解决方法:在构造器中绑定
1
| this.changeWeather = this.changeWeather.bind(this)
|
JavaScript中的bind:
1 2 3 4 5 6
| function demo() { console.log(this) } demo() const x = demo.bind({a:1, b:'b'}) x()
|
效果
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| <!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的简写方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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是只读的
1 2 3 4 5 6 7 8
| class Test extends React.Component { render() { console.log(this) return <div></div> } }
ReactDOM.render(<Test key1="value1" key2="value2" />, document.getElementById('test'))
|
批量传递props
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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'))
|
展开运算符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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
限制方法:
-
引入依赖包
1
| <script type="text/javascript" src="../../js/prop-types.js"></script>
|
-
限制标签属性:
1 2 3 4 5 6 7 8 9 10
| Person.propTypes = { name: PropTypes.string.isRequired, sex: PropTypes.string, age: PropTypes.number, speak: PropTypes.func } Person.defaultProps = { sex: '男', age: 18 }
|
-
效果:
1
| ReactDOM.render(<Person name={120} />, document.getElementById('test2'))
|
props的简写方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Person extends React.Component {
static propTypes = { name: PropTypes.string.isRequired, sex: PropTypes.string, age: PropTypes.number, speak: PropTypes.func } static defaultProps = { sex: '男', age: 18 } render() { const {name, age, sex} = this.props return ( <ul> <li>姓名:{name}</li> <li>年龄:{age+1}</li> <li>性别:{sex}</li> </ul> ) }
}
|
构造器与props
1 2 3 4
| constructor(props) { super(props) console.log('constructor',this.props); }
|
构造器是否接收props,是否传递给super,取决于是否希望在构造器中通过this访问props,极其罕见
如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。
通常,在 React 中,构造函数仅用于以下两种情况:
函数式组件使用props
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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'))
|
添加限制:
1 2 3 4 5 6 7 8 9 10 11
| Person.propTypes = { name: PropTypes.string.isRequired, sex: PropTypes.string, age: PropTypes.number, } Person.defaultProps = { sex: '男', age: 18 }
ReactDOM.render(<Person name={100}/>, document.getElementById('test3'))
|
理解
- 每个组件对象都会有props(properties的简写)属性
- 组件标签的所有属性都保存在props中
作用:
- 通过标签属性从组件外向组件内传递变化的数据
- 注意: 组件内部不要修改props数据
3.refs
refs收集多组ref
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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 - 不太推荐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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中的调用次数:
关于回调 refs 的说明
如果 ref
回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null
,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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> ) } }
|
避免多次回调:将 ref 的回调函数定义成 class 的绑定函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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的形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Demo extends React.Component {
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等)都是现用现取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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的双向数据绑定)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 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> ) } }
|
高阶函数与函数柯里化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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方括号
1 2 3 4 5 6 7 8 9 10
| 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()
等
函数的柯里化
通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
function sum(a) { return (b) => { return (c) => { return a+b+c } } } result = sum(1)(2)(3) console.log(result);
|
不用柯里化的写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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> ) }
|
组件的生命周期
理解生命周期
生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
class Demo extends React.Component { state = { opacity: 1 }
death = () => { 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() { 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()流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| class Count extends React.Component {
constructor(props) { console.log('Count - constructor') super(props) this.state = {count: 0} }
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 }
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流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| 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 {
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> <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版本
1 2 3 4 5 6 7 8
| <script type="text/javascript" src="../js/17.0.1/react.development.js"></script>
<script type="text/javascript" src="../js/17.0.1/react-dom.development.js"></script>
<script type="text/javascript" src="../js/17.0.1/babel.min.js"></script>
|
流程图
getDerivedStateFromProps()
getDerivedStateFromProps
会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null
则不更新任何内容。
此方法适用于罕见的用例,即 state 的值在任何时候都取决于 props。例如,实现 <Transition>
组件可能很方便,该组件会比较当前组件与下一组件,以决定针对哪些组件进行转场动画。
1 2 3 4 5 6 7 8 9
| 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
)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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:
用index作为key可能会引发的问题
- 若对数据进行:逆序添加、逆序删除等破坏顺序的操作:
- 会产生没有必要的真实DOM更新 ===> 界面效果没问题,但效率低
- 如果结构中包含输入类的DOM:
注意:如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key没有问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| 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亦可