1. React类组件
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
n: 0
}
}
add = () => {
this.setState(() => {
return { n: this.state.n + 1 }
})
}
render() {
return (
<div>{this.state.n}
<button onClick={this.add}>+1</button>
</div>
)
}
}
ReactDOM.render(<App />, document.querySelector('#root'))
上面是一个简单的例子,声明了一个App组件,最后将它挂载到root元素上
2. 类组件的写法
class 组件名 extends React.Component{
constructor(props){
super(props)
this.state = { // 定义state
//...
}
}
}
一个组件中有state(组件自身的数据)和props(外部传入组件的数据)
3. state
读取state----this.state
修改state-----this.setState
3.1 setState
将对组件state的更改排入队列,并通知React需要使用更新后的state重新渲染此组件及子组件。
语法:setState(updater,[callback])
参数一为带有形式参数的updater函数(state,props) => stateChange,updater的返回值会与state进行浅合并,state和props都是最新值
add(){
this.setState((state,props)=>{
return {n:state.n+1}
})
}
参数一除了接收函数外,还可以接受对象类型setState(stateChange[,callBack]),stateChange会将传入的对象浅层合并到新的state中,这种形式的setState()是异步的,并且会在同一周期内会对多个setState进行批处理
add(){
for (let i = 0; i < 100; i++) {
this.setState({ n: this.state.n + 1 });
console.log(this.state.n)//若刚开始n为0,此时,n也为0
}
}
//最后渲染在页面上的n,显示1,因为每次拿到的n都是0
注意:
setState()并不总是更新组件。它会批量延迟更新,无论在 React 事件处理程序setState()中调用多少组件,它们都只会在事件结束时产生一次重新渲染,这使得调用setState()后不能通过this.state获取到更新后的值。如果想要得到最新的state,可以使用componentDidUpdate或者setState的回调函数
- state是对应变化时组件状态的引用。React设计为不可变数据,所以不应该直接被修改,需要使用state和props构建的新对象来表示变化
this.setState({n:this.state.n+1}) - 更新总是按照它们发生的顺序进行浅层合并。所以如果第一次更新是
{a: 10},第二次是{b: 20},第三次是{a: 30},渲染状态将是{a: 30, b: 20}。对同一状态键的最新更新
参数二为可选的回调函数,它将在setState完成合并并重新渲染组件后执行。通常,建议使用compnentDisUpdate()来代替此方式
上述例子中的add如果改成传入函数
add() {
for (let i = 0; i < 100; i++) {
this.setState((state) => {
console.log(state.n)
return { n: state.n + 1 }
});
}
}
页面显示100,控制台打印0-99,因为setState函数当接受一个函数作为参数,在函数中可以得到前一个状态并返回下一个状态中,也就是说state一直都是最新的值,所以推荐在setState中传入函数
4. 绑定事件的写法
construnctor(){...}
add(){
this.setState({n:this.state.n+1})
}
render() {
return (
<div>{this.state.n}
<button onClick={this.add}>+1</button>
</div>
)
}
上面代码会报如下错误
报错的原因是因为<button onClick={this.add}>+1</button>的this.add中this指向window
React在执行上面这句代码时,相当于button.onClick.call(null,event),则this.add函数中的this为null,则在浏览器中就是window。this.add中的有使用this获取state,那么就会报错
如何让add中的this指向这个类呢?可以使用如下写法
- 第一种,通过
bind指定this的值,this.add中的this指向类,此时bind给add函数,所以add函数中的this也是指向类
<button onClick={this.add.bind(this)}>+1</button>
- 第二种,因为箭头函数不支持
this,此时this指的是外层的this,即指向类,所以可以使用
<button onClick={()=>this.add()}>+1</button>
- 第三种,写成箭头函数,作为实例属性
constructor(){
this.state = {
n:0
}
this.add=()=>{
this.setState({n:this.state.n+1})
}
}
<button onClick={this.add}>+1</button>
- 第四种,最终写法,与上面第三种写法是等价的
construnctor(){...}
add=()=>{this.setState({n:this.state.n+1})}
<button onClick={this.add}>+1</button>
写法的区别,前面两种写法的add是定义在constructor外,也就是绑定在类的原型上,被所有实例共享后面两种写法add定义在constructor内,绑定在实例上,不同的实例都有属于各自的add属性
5. props
import React from 'react';
import ReactDOM from 'react-dom';
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
n: 0,
message: '传给子组件的消息'
}
}
add = () => { this.setState({ n: this.state.n + 1 }) }
render() {
return (
<div>{this.state.n}
<button onClick={this.add}>+1</button>
<Child message={this.state.message} n={this.state.n} addClick={this.add}>hello</Child>
</div>
)
}
}
class Child extends React.Component {
constructor(props) {
super(props)// 初始化props,this.props就指向外部数据对象的地址
console.log(props)
}
render() {
return (
<div>
{this.props.message}
<div>{this.props.children}</div>
{this.props.n}
<button onClick={this.props.addClick}>+1</button>
</div>
)
}
}
ReactDOM.render(<App />, document.querySelector('#root'))
<Child message={this.state.message} n={this.state.n} addClick={this.add}>hello</Child>上述代码会以{message: '传给子组件的消息', n: 0, children: 'hello', addClick: ƒ}形式传给子组件
super(props) 初始化props,this.props就指向外部数据对象的地址
修改props,不可以修改props,通知传入数据的组件,自己修改
6. 生命周期
6.1 constructor()
初始化props,state
6.2 shouldComponentUpdate()
return false 表示数据没变,不需要重新生成虚拟DOM,从而不会更新UI
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
n: 0
}
}
add = () => {
this.setState((state) => ({
n: state.n + 1
}))
this.setState((state) => ({
n: state.n - 1
}))
}
render() {
console.log('触发了')
return (
<div>
{this.state.n}
<button onClick={this.add}>+1</button>
</div>
)
}
}
上述代码中,由于先加一再减一,页面上的n是没有改变值的,但是每点一次,render就会被调用一次
-
为什么不是两次,因为
setState批量处理更新 -
为什么
state最终没变,还是会触发更新,因为{n:1}和{n:1},是两个对象,地址不同。 -
React通过Diff算法,比较两次虚拟DOM,发现没有任何变化,所以DOM不会更新
既然没有更新,如何控制不触发render?使用shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState) {
if (nextState.n === this.state.n) {
return false
} else {
return true
}
}
如果有多个n这样的元素,想要实现,值变化了才触发render,难道每一个都要手动设置吗?不需要,使用PureComponent就好,如下例所示,render中的输出,只有首次渲染时才会打印出
class App extends React.PureComponent {
constructor(props) {
super(props)
this.state = {
n: 0
}
}
add = () => {
this.setState((state) => ({
n: state.n + 1
}))
this.setState((state) => ({
n: state.n - 1
}))
}
render() {
console.log('触发了')
return (
<div>
{this.state.n}
<button onClick={this.add}>+1</button>
</div>
)
}
}
PureComponent会在render之前将新旧state、props进行浅对比,只会对比一层,所以对于简单变量会比较高效,对于数组、对象等引用类型,只有内存地址不同时才会渲染
class App extends React.PureComponent {
constructor(props) {
super(props)
this.state = {
colorArr: ['red', 'yellow', 'blue']
}
}
delete = () => {
this.setState((state) => {
state.colorArr.pop()
return { colorArr: state.colorArr }
})
}
/* 使用下面的detele,就会触发render,数组对应的地址已经改变
delete = () => {
let newArr = [...this.state.colorArr]
newArr.pop()
this.setState(() => ({ colorArr: newArr }))
}
*/
render() {
console.log('触发了')
let { colorArr } = this.state
return (
<div>
<ul>
{colorArr.map((item, index) => {
return <li key={index}>{item}</li>
})}
</ul>
<button onClick={this.delete}>删除</button>
</div>
)
}
}
上述代码中,点击删除,不会触发render,页面也不会重新渲染,因为PureComponent只进行浅比较,colorArr地址没变,不会触发render
当以下几种情况推荐使用PureComponent:
state/props是不可变的对象state/props是普通类型变量- 当时据改变时,使用
forceUpdate
使用PureComponent,不推荐再使用shouldComponentUpdate
6.3 render() 创建虚拟DOM
只有一个根元素,
也可以使用虚拟标签
<React.Fragment>
...//包含多个统计标签
</React.Fragment>
简写<></>
onClick = ()=>{
this.setState((state)=>({
n:state.n+1
}))
}
render(){
let message
if(this.state.n%2===0){
message = <span>偶数</span>
}else{
message = <span>奇数</span>
}
return (
<>
{message}
<button onClick={this.onClick}>+1</button>
</>
)
}
6.4 componentDidMount()
当组件已经渲染到DOM中执行此方法,首次渲染会执行此方法
只有当虚拟DOM变成真实DOM后,我们才能获取到,如果有依赖DOM操作的代码可以放在此处
官方推荐在此时发起AJAX请求,加载数据
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
width: undefined
}
this.divRef = React.createRef() // 创建ref,可以通过此属性获取React元素
}
componentDidMount() {
// const div = document.querySelector('#box')
const div = this.divRef.current //获取当前值
const { width } = div.getBoundingClientRect()
console.log(width)
this.setState(() => ({ width: width }))
}
render() {
return (
<div id="box" ref={this.divRef}>div的宽度{this.state.width}</div>
)
}
}
6.5 componentDidUpdate(prevProps,prevState,snapshot)
组件更新完成后触发,首次渲染不会触发,数据有更新时才会
可以在此处通过AJAX发起请求,更新数据
一般不推荐在此处调用setState,因为数据更新后又会调用此钩子函数,会进入循环,除非在if条件下使用,比如满足某些条件时更新数据
6.6 componentWillUnmount()
组件即将移除时执行
unmount过的组件不会再次被mount
6.7 其他钩子函数
还有以下生命周期函数,比较少用
static getDerivedStateFromProps()
getSnapshotBeforeUpdate()
static getDerivedStateFromError()
componentDidCatch()
了解详情可见官网介绍