本文最后更新于:2023年8月24日 晚上
初识React
相对于js渲染页面来说,react使用空间换取时间,渲染相似数据时,只改变改动的数据
js渲染过程:遍历数据添加dom ——》浏览器dom重新渲染
react渲染过程:遍历数据添加虚拟dom ——》虚拟dom于上次比对,只更新改动的地方 ——》浏览器dom重新渲染
以下都是基于React16.x版本开发的单页内容
Hello, React
创建html,引入react.development.js(核心库)和react-dom.development.js(dom的操作库),和vue只需要引入一个不一样,react需要引入两个,引入顺序也必须为先引入核心库,再引入babel.min.js作为jsx与js的转换
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
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script crossorigin src="https://unpkg.com/react@16.8.6/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16.14.0/umd/react-dom.development.js"></script> <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script> </head> <body>
<div id="app"></div> </body>
<script type="text/babel"> const VDOM = <h1>Hello,React</h1> ReactDOM.render(VDOM, document.getElementById('app')) </script> </html>
|
使用jsx语法的时候,script的type就必须使用text/babel
注意 ReactDOM 别写错,DOM是大写,官网的18版本中render函数已经淘汰了
React中使用jsx与js差别
现在需要创建一个这样的标签
1 2 3 4 5
| <div id="app"> <h1 id="title"> <span>Hello React</span> </h1> </div>
|
使用JS
1 2 3 4
| const VDOM = React.createElement('h1', {id: 'title'}, React.createElement('span', {}, 'Hello React'))
ReactDOM.render(VDOM, document.getElementById('app'))
|
这是使用了React创建的虚拟dom,对应的原生js真实dom如下:
1 2 3 4 5 6 7
| const h1 = document.createElement('h1') h1.id = 'title' const span = document.createElement('span') span.innerText = 'Hello React' h1.appendChild(span) const app = document.getElementById('app') app.appendChild(h1)
|
使用JSX(javascript xml)
1 2 3 4 5 6 7 8
| const VDOM = ( <h1 id="title"> <span>Hello React</span> </h1> )
ReactDOM.render(VDOM, document.getElementById('app'))
|
使用jsx的语法编译后还是上面的样子,只不过自己写的舒服
react jsx常识
虚拟dom实际上是一个一般对象:Object
标签中混入js表达式要使用{}
1 2 3 4 5 6 7 8 9 10 11
| const myId = 'tITlE' const content = 'Hello React'
const VDOM = ( <h1 id={myId}> <span>{content}</span> </h1> )
ReactDOM.render(VDOM, document.getElementById('app'))
|
样式的类名指定不要使用class,要使用className
在jsx的模板html中,class属性已经被替换为className了,因为jsx怕和es6中的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 27 28 29 30 31 32 33 34 35 36 37 38 39
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script crossorigin src="https://unpkg.com/react@16.8.6/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16.14.0/umd/react-dom.development.js"></script> <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<style> .title { width: 200px; background: #eee; } </style> </head> <body>
<div id="app"></div> </body>
<script type="text/babel">
const myId = 'tITlE' const content = 'Hello React'
const VDOM = ( <h1 id={myId.toLowerCase()} className={myId.toLowerCase()}> <span>{content}</span> </h1> ) ReactDOM.render(VDOM, document.getElementById('app')) </script> </html>
|
使用style设置样式的时候,需要使用键值对
如下:
1 2 3
| <h1 id={myId.toLowerCase()} className={myId.toLowerCase()} style={{ fontSize: '50px' }}>
|
style后面第一个{}表示这是js表达式,第二个才是样式的键值对
在模板字符串中只能有一个根标签
如需要在页面上再渲染一个input
1 2 3 4 5 6 7 8 9 10
| const VDOM = ( <div> <h1 id={myId.toLowerCase()} className={myId.toLowerCase()} style={{ fontSize: '50px' }}> <span>{content}</span> </h1> <input type="text"/> </div> )
|
标签必须闭合
如input在html上写的时候:
1 2 3
| <div id="app"> <input type="text"> </div>
|
使用jsx的时候,就需要对input标签做下闭合
代码见上一个中的input标签
标签首字母大小写区别
- 小写字母开头, 则转换为html中同名元素,若html中无同名标签,就报错
- 大写字母开头,react就会渲染组件,若组件没定义,就报错
遍历渲染
在jsx中使用数组的时候,会自动的遍历,如:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const demo = ['1','2','3']
const VDOM = ( <div> <h1>前端js框架列表</h1> <ul> <li>{demo}</li> </ul> </div> )
ReactDOM.render(VDOM, document.getElementById('app'))
|
页面显示的就是123,但是对于复杂的结构,无法做到自动遍历,如下:
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
| const data = [ { id: 1, name: 'Angular' }, { id: 2, name: 'React' }, { id: 3, name: 'Vue' } ]
const VDOM = ( <div> <h1>前端js框架列表</h1> <ul> <li key={v.id}>{data}</li> </ul> </div> )
ReactDOM.render(VDOM, document.getElementById('app'))
|
这样会导致报错,所以遍历复杂的数据就要在html标签中插入js代码块了
1 2 3 4 5 6 7 8
| const VDOM = ( <div> <h1>基础渲染</h1> <ul> {data.map(v => <li key={v.id}> {v.name} </li>)} </ul> </div> )
|
分析:
data.map(v => <li> {v.name} </li>)返回一个改变了的数组,然后由react自动遍历
完整结构:
1 2 3 4 5 6 7 8 9 10
| const renderData = data.map(v => <li> {v.name} </li>)
const VDOM = ( <div> <h1>基础渲染</h1> <ul> {renderData} </ul> </div> )
|
React的组件开发
组件开发分为两个部分:
- 函数式组件(用于函数定义的组件,适用于【简单组件】的定义)
- 类式组件(适用于【复杂组件】的定义)
函数式组件
见名知意,使用函数开发,将html模板封装在函数中使用,如下:
1 2
| const FInput = () => <input className={1 === 1 ? 'input1' : 'input2'} type="text" placeholder="请输入内容"/> ReactDOM.render(<FInput/>, document.getElementById('app'))
|
注意点:
- 函数必须要有返回值
- 因为react中组件的首字母需要大写,所以函数名也需要大写
- 记得对组件标签做自闭合
ReactDOM.render(<FInput/>实现步骤:
- React解析组件标签,找到了自定义的组件(没找到就报错)
- 发现组件是函数定义的,随后调用该函数,将接收返回的虚拟dom转为真实dom后渲染
类式组件
使用es6中的类声明一个组件,步骤如下:
- 创建一个类首字母大写(组件名就是类名),继承
React.Component
- 创建
render方法,返回内容就是组件内容
1 2 3 4 5 6
| class FInput extends React.Component { render() { return <input className={1 === 2 ? 'input1' : 'input2'} type="text" placeholder="请输入内容"/> } } ReactDOM.render(<FInput/>, document.getElementById('app'))
|
render和ReactDOM.render两个render方法没有关系
内部渲染过程如下:
- React解析组件标签,找到
FInput组件
- 发现这个组件是使用类定义的,随后使用new创建该类的实例
- 使用实例对象调用了render方法,获取返回的虚拟dom
组件实例的三大属性
只有使用类定义的组件有实例属性
核心属性1:state
理解
- state 是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
- 组件被称为“状态机”,通过更新组件的state来更新对应的页面的显示(重新渲染组件)
强烈注意
- 组件中render方法中的this为组件实例对象
- 组件自定义的方法中this为undefined,如何解决?
- 强制绑定this:通过函数对象的bind()
- 箭头函数
- 状态数据,不能直接修改或更新
案例一
效果:渲染一个h1标签,点击标签改变其中的文字
内容:需要了解js中this的指向问题(见js中this指向文章)和jsx语法上的点击事件
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
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script crossorigin src="https://unpkg.com/react@16.8.6/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16.14.0/umd/react-dom.development.js"></script> <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script> </head> <body> <div id="app"></div> </body> <script type="text/babel"> class FInput extends React.Component { constructor(props) { super(props); this.state = { isHot: true } } render() { return <h1 onClick={this.changeState}>今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1> }
changeState = () => {
this.setState({ isHot: !this.state.isHot }) } }
ReactDOM.render(<FInput/>, document.getElementById('app')) </script> </html>
|
主要注意的事:
state属性是直接定义在构造器中的
onClick后面的函数是赋值在onClick回调中的,所以类中的this是undefined(上面使用了箭头函数,所以this可以正常使用)
state中的状态不可以直接更新,需要使用setState方法更新
针对以上第二点,有三个解决方法(箭头函数,赋值this,使用bind)
方法一(使用箭头函数)
上面就是
方法二(暂存this)
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
| let that
class FInput extends React.Component { constructor(props) { super(props); this.state = { isHot: true } that = this }
render() { return <h1 onClick={this.changeState}>今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1> }
changeState() {
that.setState({ isHot: !that.state.isHot }) } }
ReactDOM.render(<FInput/>, document.getElementById('app'))
|
原理就是利用构造器中this一定为类的实例对象
方法三(使用bind函数)
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 FInput extends React.Component { constructor(props) { super(props); this.state = { isHot: true } this.changeState = this.changeState.bind(this) }
render() { return <h1 onClick={this.changeState}>今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1> }
changeState() {
this.setState({ isHot: !this.state.isHot }) } }
ReactDOM.render(<FInput/>, document.getElementById('app'))
|
bind():在某个对象后使用bind函数可以修改此对象的this指向,并返回一个新的函数,onClick绑定的函数也变成了新的函数,因为名字相同,但是新的changeState是在类的实例对象上,老的是在类的原型链上,所以同名就先找新的
方法是特殊的属性
最简结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class FInput extends React.Component { state = { isHot: true }
render() { return <h1 onClick={this.changeState}>今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1> }
changeState = () => { this.setState({ isHot: !this.state.isHot }) } }
ReactDOM.render(<FInput/>, document.getElementById('app'))
|
核心属性2: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 28 29 30 31 32 33 34 35 36 37 38 39
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title>
<script crossorigin src="https://unpkg.com/react@16.8.6/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16.14.0/umd/react-dom.development.js"></script> <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script> </head> <body> <div id="demo1"></div> <div id="demo2"></div> <div id="demo3"></div> </body>
<script type="text/babel"> class Ul extends React.Component { render() { const {name, age, cName} = this.props return ( <ul> <li>姓名:{name}</li> <li>年龄:{age}</li> <li>班级:{cName}</li> </ul> ) } }
ReactDOM.render(<Ul name='FSAN1' age='18' cName='rj'/>, document.getElementById('demo1')) ReactDOM.render(<Ul name='FSAN2' age='19' cName='rj'/>, document.getElementById('demo2')) ReactDOM.render(<Ul name='FSAN3' age='20' cName='rj'/>, document.getElementById('demo3')) </script> </html>
|
属性多个传递
利用babel+react的特性,就可以在标签中使用扩展运算符来传递一个对象中所有属性
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
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title>
<script crossorigin src="https://unpkg.com/react@16.8.6/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16.14.0/umd/react-dom.development.js"></script> <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script> </head> <body> <div id="demo1"></div> <div id="demo2"></div> <div id="demo3"></div> </body>
<script type="text/babel"> class Ul extends React.Component { render() { const {name, age, cName} = this.props return ( <ul> <li>姓名:{name}</li> <li>年龄:{age}</li> <li>班级:{cName}</li> </ul> ) } }
const data = [ { name: 'FSAN1', age: 18, cName: 'rj' }, { name: 'FSAN2', age: 19, cName: 'rj' }, { name: 'FSAN3', age: 20, cName: 'rj' } ] data.forEach((v, i) => ReactDOM.render( <Ul {...v}/>, document.getElementById(`demo${i + 1}`)) ) </script> </html>
|
props输入类型校验
旧方式(react15.5已弃用)
1 2 3 4
| Ul.propTypes = { name: React.PropTypes.string.isRequired, age: React.PropTypes.number }
|
新方式
引入prop-types这个库
1
| <script src="https://cdn.staticfile.org/prop-types/15.8.1/prop-types.js"></script>
|
然后对组件上的propType属性定义:
1 2 3 4
| Ul.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number }
|
完整代码:
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
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title>
<script crossorigin src="https://unpkg.com/react@16.8.6/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16.14.0/umd/react-dom.development.js"></script> <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script> <script src="https://cdn.staticfile.org/prop-types/15.8.1/prop-types.js"></script> </head> <body> <div id="demo1"></div> </body>
<script type="text/babel"> class Ul extends React.Component { render() { const {name, age, cName, speak} = this.props speak() return ( <ul> <li>姓名:{name}</li> <li>年龄:{age + 1}</li> <li>班级:{cName ? cName : '软件技术'}</li> </ul> ) } } Ul.propTypes={ name: PropTypes.string.isRequired, age: PropTypes.number, speak: PropTypes.func }
const speak = () => { console.log('这是一个函数') } ReactDOM.render(<Ul name={'FSAN'} age={18} speak={speak}/>, document.getElementById(`demo1`)) </script> </html>
|
PropTypes支持类似链式一样的写法:isRequired表示必填,函数要使用func表示
props默认值
当props参数不传递的时候,页面不会出现错误,只会显示为空,这时候就可以设置默认值
1 2 3 4 5
| Ul.defaultProps = { name: 'FSAN', age: 19 }
|
简化props类型和默认值
以上的类型校验和设置默认值都是在设置Ul这个类静态属性,又因为这是一个组件,所以这类东西应该放在组件内部才合理
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
| class Ul extends React.Component {
static propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number, speak: PropTypes.func }
static defaultProps = { name: 'FSAN', age: 19 }
render() { const {name, age, cName, speak} = this.props speak() return ( <ul> <li>姓名:{name}</li> <li>年龄:{age + 1}</li> <li>班级:{cName ? cName : '软件技术'}</li> </ul> ) } }
const speak = () => { console.log('这是一个函数') }
ReactDOM.render(<Ul name={'FSAN'} age={18} speak={speak}/>, document.getElementById(`demo1`))
|
构造器的作用
有无构造器只有一个地方有区别:在构造器内部是否可以使用this.props
有构造器,接不接收props写不写super都一样
只有在构造器中接受props并使用super传递props才能在构造器内部使用this.props
总结:
- 大多时候不需要写构造器,除非你要在构造器中使用
props,那也用不着this
在函数式组件中使用props传参
组件实例的有三大属性,只有class定义的组件才有组件实例,函数式组件只有一个props(最新的可以使用hooks)
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
| const Ul = (props) => { const {name, age, cName} = props return ( <ul> <li>姓名:{name}</li> <li>年龄:{age + 1}</li> <li>班级:{cName}</li> </ul> ) }
Ul.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number }
Ul.defaultProps = { name: 'FSAN', age: 19, cName: '软件技术' }
ReactDOM.render(<Ul name={'FSAN'} age={18}/>, document.getElementById(`demo1`))
|
核心属性3:refs
字符串形式(最老写法)
存在效率上的问题(过时并在未来版本可能会淘汰)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Cl extends React.Component {
logInput = (e) => { console.log(e) console.log(this.refs) }
render() { return ( <div> <input type="text" ref="input1"/> <button onClick={this.logInput} style={{margin: '20px'}}>输出结果</button> <input onBlur={({target}) => this.logInput(target)} ref="input2" type="text"/> </div> ) } }
ReactDOM.render(<Cl/>, document.getElementById('app'))
|
回调形式(更新时会有点小问题)
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 Cl extends React.Component { state = { num: 1 }
logInput = () => { console.log(this.inputRef.value) }
render() { const {num} = this.state return ( <div> <div>渲染次数:{num}</div> <input type="text" ref={e => { this.inputRef = e; console.log('ref加载:', e) }}/> <button onClick={this.logInput} style={{margin: '20px'}}>输出结果</button> <button onClick={() => this.setState({num: num + 1})}>重新渲染</button> </div> ) } }
ReactDOM.render(<Cl/>, document.getElementById('app'))
|
在页面中的元素发生变化时(render重新渲染),会发现ref加载打印了两次,第一次input的dom对象为null,第二次正常打印,这就是react先清空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 27 28
| class Cl extends React.Component { state = { num: 1 }
logInput = () => { console.log(this.inputRef.value) }
saveInput = (e) => { this.inputRef = e; console.log('ref加载:', e) }
render() { const {num} = this.state return ( <div> <div>渲染次数:{num}</div> <input type="text" ref={this.saveInput}/> <button onClick={this.logInput} style={{margin: '20px'}}>输出结果</button> <button onClick={() => this.setState({num: num + 1})}>重新渲染</button> </div> ) } }
ReactDOM.render(<Cl/>, document.getElementById('app'))
|
API创建形式(推荐)
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
| class Cl extends React.Component { state = { num: 1 } inputRef1 = React.createRef()
logInput = () => { console.log(this.inputRef1.current.value) }
render() { const {num} = this.state return ( <div> <div>渲染次数:{num}</div> <input type="text" ref={this.inputRef1}/> <button onClick={this.logInput} style={{margin: '20px'}}>输出结果</button> <button onClick={() => this.setState({num: num + 1})}>重新渲染</button> </div> ) } }
ReactDOM.render(<Cl/>, document.getElementById('app'))
|
使用React的createRef这个函数去创建一个响应对象,和vue3一样
组件的两个赋值状态
受控组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class C1 extends React.Component { toLogin = (e) => { e.preventDefault() alert(`用户名:${this.username.value},密码:${this.password.value}`) }
render() { return ( <form action="" onSubmit={this.toLogin}> 用户名:<input ref={e => this.username = e} type="text"/> 密码:<input ref={e => this.password = e} type="password"/> <button>登录</button> </form> ) } }
ReactDOM.render(<C1/>, document.getElementById('app'))
|
受控组件的展示就是,当我们点击登录按钮才会去获取input的值,发出登录请求(默认get)
非受控组件
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
| class C1 extends React.Component { state = { username: '', password: '' }
saveUsername = (e) => { this.setState({ username: e.target.value }) } savePassword = (e) => { this.setState({ password: e.target.value }) }
toLogin = (e) => { e.preventDefault() const {username, password} = this.state alert(`用户名:${username},密码:${password}`) }
render() { return ( <form action="" onSubmit={this.toLogin}> 用户名:<input onChange={this.saveUsername} type="text"/> 密码:<input onChange={this.savePassword} type="password"/> <button>登录</button> </form> ) } }
ReactDOM.render(<C1/>, document.getElementById('app'))
|
在非受控组件中,input就值就像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 31 32
| class C1 extends React.Component { state = { formData: { username: '', password: '' } }
saveFormData = k => e => { const {formData} = this.state formData[k] = e.target.value this.setState({formData}) }
toLogin = e => { e.preventDefault() const {formData} = this.state alert(`用户名:${formData.username},密码:${formData.password}`) }
render() { return ( <form action="" onSubmit={this.toLogin}> 用户名:<input onChange={this.saveFormData('username')} type="text"/> 密码:<input onChange={this.saveFormData('password')} type="password"/> <button>登录</button> </form> ) } }
ReactDOM.render(<C1/>, document.getElementById('app'))
|
柯里化(currying)指的是将一个多参数的函数拆分成一系列函数,每个拆分后的函数都只接受一个参数(unary)
简化说明
1
| onChange={this.saveFormData('username')}
|
这样是将saveFormData这个函数的返回值作为回调函数,所以如果这个函数内这样写就是错误的
1 2 3
| saveFormData = k => { console.log(k) }
|
这样只会打印一次k,之后点击就是无反应,因为这个函数返回时的是undefined
所以如果要使用这种onChange绑定的话,返回的一定是一个函数
1 2 3 4 5
| saveFormData = k => { return e => { } }
|
这时候,传进去的username被k接受,然后返回一个函数,当作点击回调,回调时没有参数,e就是event
函数柯里化例子
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
| const getNum = (a, b) => { a++ return a + b } console.log(getNum(1, 2));
const getNumPlus = a => { a++ return b => { return a + b } }
const demo1 = getNumPlus(1)(3) console.log(demo1)
const demo2 = getNumPlus(1) console.log(demo2(4)); console.log(demo2(5));
const endGetNum = a => b => a+++b console.log(endGetNum(1)(2));
|
函数生命周期
案例一
需要实现文字的透明度变化,和取消元素挂载,需要包括取消组件挂载后关闭定时器
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
| class Demo extends React.Component { state = { opacity: 1, intervalTimer: '' }
removeComponent = () => { ReactDOM.unmountComponentAtNode(document.getElementById('app')) }
componentDidMount() { this.intervalTimer = setInterval(() => { let o = this.state.opacity if (o <= 0) o = 1 this.setState({ opacity: o - 0.1 }) }, 200) }
componentWillUnmount() { clearInterval(this.intervalTimer) }
render() { return ( <div> <h1 style={{opacity: this.state.opacity}}>React学不会怎么办</h1> <button onClick={this.removeComponent}>不学了</button> </div> ) } }
ReactDOM.render(<Demo/>, document.getElementById('app'))
|
知识点:
unmountComponentAtNode()卸载组件
componentWillUnmount()组件卸载及销毁之前调用
componentDidMount()组件挂载完毕后调用
完整生命周期(17.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
| class Count extends React.Component { state = { count: 0 }
constructor(props) { console.log('constructor(构造器)') super(props); }
componentWillMount() { console.log('componentWillMount(组件将要挂载)') }
render() { console.log('render(组件渲染)') return ( <div> <div>当前计数:{this.state.count}</div> <button onClick={e => this.setState({count: this.state.count + 1})}>增加</button> </div> ) }
componentDidMount() { console.log('componentDidMount(组件挂载完毕)') }
componentWillUnmount() { console.log('componentWillUnmount(组件销毁或卸载之前)') } }
ReactDOM.render(<Count/>, document.getElementById('app'))
|
更新数据时
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
|
shouldComponentUpdate(nextProps, nextState, nextContext) { console.log('shouldComponentUpdate', nextProps, nextState, nextContext) return true }
componentWillUpdate(nextProps, nextState, nextContext) { console.log('componentWillUpdate', nextProps, nextState, nextContext) }
componentDidUpdate(prevProps, prevState, snapshot) { console.log('componentDidUpdate', prevProps, prevState, snapshot) }
|
强制更新
1
| <button onClick={e => this.forceUpdate()}>强制渲染</button>
|
强制更新是不走阀门的,直接到componentWillUpdate
传递参数时
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
| class A extends React.Component { state = { num: 0 }
render() { return ( <div> <div>这是父组件A</div> <button onClick={e => this.setState({num: this.state.num + 1})}>增加子组件的数字</button> <B num={this.state.num}/> </div> ) } }
class B extends React.Component {
componentWillReceiveProps(nextProps, nextContext) { console.log('componentWillReceiveProps', nextProps) }
render() { return ( <div> <div>这是子组件B</div> <div>显示的数字为:{this.props.num}</div> </div> ) } }
ReactDOM.render(<A/>, document.getElementById('app'))
|
知识点:
componentWillReceiveProps
总结
最常用的三个钩子:
componentDidMount()组件挂载完毕之后
render()
componentWillUnmount()组件销毁或卸载之前
新的生命周期以及变化(17.x)
除了componentWillUnmount()之外,其他所有有will的生命周期在新版本都需要在前面加上UNSAFE(unsafe这里并不是表示不安全,而是在未来使用异步渲染的时候可能会出现问题),具体变化的钩子如下
componentWillMount()组件挂载之前 ==》UNSAFE_componentWillMount()
componentWillUpdate()组件更新之前 ==》UNSAFE_componentWillUpdate()
componentWillReceiveProps()组件接受参数之前 ==》UNSAFE_componentWillReceiveProps()
新增两个生命周期
| 生命周期函数 |
说明 |
getDerivedStateFromProps() |
返回对象对state的更新(罕见使用:若state的值在任何时候都取决于props),派生状态会导致代码冗余,并使组件难以维护 |
getSnapshotBeforeUpdate() |
更新时在render后,在视图更新之前执行,可以保存更新前的状态,返回的值传递给componentDidUpdate()(组件更新完毕),此用法并不常见 |
案例二(控制滚动条)
类似直播的弹幕,可随时跟踪最新也可以停止查看
新增在顶部
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 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script> <style> * { padding: 0; margin: 0; }
.divBox { width: 400px; height: 400px; overflow: auto; margin: auto; border: 1px solid #ccc; }
.divBox div { height: 60px; line-height: 60px; cursor: pointer; }
.divBox div:hover { background: #f4f4f4; } </style> </head> <body> <div id="app"></div> </body> <script type="text/babel"> class Demo extends React.Component { state = { arrayList: [] } timer divBoxRef = React.createRef()
componentDidMount() { this.timer = setInterval(() => { this.setState({ arrayList: [`这是消息${this.state.arrayList.length + 1}`, ...this.state.arrayList] }) }, 500) }
getSnapshotBeforeUpdate(prevProps, prevState) { const {scrollTop} = this.divBoxRef.current if (scrollTop === 0) { return null } else { return this.divBoxRef.current.scrollHeight } }
componentDidUpdate(prevProps, prevState, height) { if (height) { let {scrollTop, scrollHeight} = this.divBoxRef.current scrollTop += scrollHeight - height this.divBoxRef.current.scrollTop = scrollTop } }
render() { return ( <div> <div className="divBox" ref={this.divBoxRef}> {this.state.arrayList.map((v, i) => (<div key={i}>{v}</div>))} </div> <button onClick={e => { clearInterval(this.timer); this.divBoxRef.current.scrollHeight = 10 }}>停止 </button> </div> ) } }
ReactDOM.createRoot(document.getElementById('app')).render(<Demo/>) </script> </html>
|
新增在底部
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 77 78 79 80 81
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script> <style> * { padding: 0; margin: 0; }
.divBox { width: 400px; height: 400px; overflow: auto; margin: auto; border: 1px solid #ccc; }
.divBox div { height: 60px; line-height: 60px; cursor: pointer; }
.divBox div:hover { background: #f4f4f4; } </style> </head> <body> <div id="app"></div> </body>
<script type="text/babel"> class Demo extends React.Component {
state = { arrayList: [], } divBoxRef = React.createRef()
componentDidMount() { setInterval(() => { this.setState({ arrayList: [...this.state.arrayList, `这是消息${this.state.arrayList.length + 1}`] }) }, 500) }
getSnapshotBeforeUpdate(prevProps, prevState) { const {scrollTop, scrollHeight, clientHeight} = this.divBoxRef.current if (scrollTop + clientHeight === scrollHeight) return scrollHeight return null }
componentDidUpdate(prevProps, prevState, snapshot) { if (snapshot) { const {scrollTop, scrollHeight} = this.divBoxRef.current this.divBoxRef.current.scrollTop += scrollHeight - snapshot } }
render() { return ( <div className="divBox" ref={this.divBoxRef}> {this.state.arrayList.map((v, i) => (<div key={i}>{v}</div>))} </div> ) } }
ReactDOM.createRoot(document.getElementById('app')).render(<Demo/>) </script> </html>
|
diffing算法
渲染时对比新旧标签的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没有问题的