React Class组件
一.class组件的创建方式
创建 class 组件
两种方式
//第1种.ES5方式(过时) import React from 'react' const A = React.createClass({ render() { return ( <div>hi</div> ) } }) export default A // 由于ES5不支持class,才会有这种方式
//第2种.ES6方式(推荐) import React from 'react'; class B extends React.Component { constructor(props) { super(props); } render() { return ( <div>hi</div> ) } } export default B;
浏览器不支持ES6怎么办?
用webpack+babel将ES6翻译成ES5即可
Props外部数据
对组件来说组件内就是内部,组件外就是外部\
1.传入props给B组件
class Parent extends React.Component { constructor(props){ super(props) this.state = {name:'frank'} } onClick = ()=>{} render(){ return <B name={this.state.name} onClick={this.onClick}>hi</B> } } //外部数据被包装为一个对象 //{name:'frank',onClick:...,children:'hi'} //此处 onClick 是一个回调
2.初始化
class B extends React.Component { constructor(props) { super(props); } render(){} } //要么不初始化,即不写constructor //要么初始化,且必须写全套(不写super直接报错)
效果
这么做了之后,this.props就是外部数据对象的地址了。
3.读取props
class B extends React.Component { constructor(props) { super(props); } render(){ return <div onClick={this.props.onClick}> {this.props.name} <div> {this.props.children} </div> </div> } } // this.props.xxx读取
4.怎么写props?
永远不准对props进行任何的改写,不准写props!
//不要这样写 this.props={ 另一个对象} this.props.xxx='hi'
相关钩子
ComponentWillReceiveProps钩子(被弃用)
当组件接收新的props时,会触发此钩子
面试题
Props的作用
1.接受外部数据
只能读不能写,外部数据由父组件传递
2.接收外部函数
在恰当的时机,调用该函数。比如说当某个div被点击时调用
该函数一般是父组件的函数
State(读) & setState(写)
1.初始化
class B extends React.Component{ constructor(props) { super(props); this.state = { //初始化 user: {name:'frank', age:18} } } render() { ... } } }
2.读写State
读用this.state
this.state.xxx.yyy.zzz
写用this.setState(???,fn)
第1个参数有2种情况:
1' this.setState(newState,fn)
⚠️注意:setState不会立即改变this.state,会在当前代码运行完后,再去更新this.state,从而触发UI更新
2' this.setState((state,props)=>newState,fn)
这种方式的state反而更易于理解
fn
会在写入成功后执行
写时会shallow merge
SetState会自动将新state与旧state进行一级合并
二、React有如下生命周期:
1.constructor() 构造,在这里初始化state 2.shouldComponentUpdate() 是否应该更新,return false阻止更新 3.render() 渲染,创建虚拟DOM 4.componentDidMount() 组件已挂载(已出现在页面) 5.componentDidUpdate() 组件已更新,当组件更新之后就会执行该函数 6.componentWillUnmount() 当组件将要卸载/死亡时就会执行该函数
一.constructor
用途
- 初始化 props
- 初始化 state,但此时不能调用 setState
- 用来写 bind this
constructor(){ /* */ this.onClick = this. onClick.bind(this) } //可以用新语法代替 onClick = ()=> {} constructor(){ /* */ }
二.shouldComponentUpdate
是否应该更新,return false阻止更新
PureComponent是shouldComponentUpdate的优化,推荐用PureComponent。
用途
返回true表示不阻止UI更新
返回false表示阻止UI更新\
面试题
请问 shouldComponentUpdate 有什么用?
这个钩子/生命周期允许我们手动判断是否要进行组件更新,我们可以根据应用场景灵活的设置返回值,以避免不必要的更新
示例:+1``-1
数值不变,render仍旧会执行
import React from "react" class App extends React.Component { constructor(props) { super(props) this.state = { n: 1 } } onClick = () => { this.setState(state => ({ n: state.n + 1 })) this.setState(state => ({ n: state.n - 1 })) } shouldComponentUpdate(newProps, newState) { if (newState.n === this.state.n) { return false } else { return true } } render() { console.log('render了一次') return ( <div>App <div> {this.state.n} <button onClick={this.onClick}>+1</button> </div> </div> ) } } export default App
补充:JS中{}
有个bug,如果你要返回一个对象,最好用()
扩起来。
Render是每次都执行了,重复执行。有没有办法阻止render不必要的重复执行?用shouldComponentUpdate
React.shouldComponentUpdate
newProps就算用不到也不能删,占位的,删掉的话newState就变成第1个参数了
shouldComponentUpdate(newProps, newState){ if (newState.n === this.state.n) { return false } else { return true } } 可以这样写 shouldComponentUpdate(){ //arguments的用法 if (arguments[1].n === this.state.n) { return false } else { return true } }
PureComponent的用法(推荐)
PureComponent是shouldComponentUpdate的优化
用法:只需要修改继承React.PureComponent
class App extends React.PureComponent{ ... }
React提供了PureComponent给你优化,可代替shouldComponentUpdate。 PureComponent 会在 render 之前对比新旧state
的每一个key,以及新旧props
的每一个key。如果所有key的值全都一样就不会render,如果有任何一个key的值不同,就会 render。
三.生命周期之 render
渲染,创建虚拟DOM
用途详解
功能1.return一个虚拟DOM
表示DOM元素的对象旧就叫做虚拟DOM,return返回的<div>
是一个虚拟DOM元素的对象。虚拟DOM就是个对象。
虚拟DOM长啥样?
render() { const x = ( <div>App <div> {this.state.n} <button onClick={this.onClick}>+1</button> </div> </div> ) console.log(x) return x }
功能2.只能有一个根元素
如果有2个元素就要用<React.Fragment>
包起来。
<React.Fragment></React.Fragment>
可以简写为<></>
render() { return ( <> <div>div1</div> <div>div2 <div> {this.state.n} <button onClick={this.onClick}>+1</button> </div> </div> </> )}
好处:只是做占位的,渲染的时候并不存在。
技巧详解
1.render里面可以写if...else
、?:
表达式
可以写if...else...
、?:
表达式(推荐)、&&
render() { let message if (this.state.n % 2 === 0) { message = <div>偶数</div> } else { message = <div>奇数</div> } return ( <> {message} <button onClick={this.onClick}>+1</button> </> ) } //也可以直接这样 render() { return ( <> {this.state.n % 2 === 0 ? <div>偶数</div> :<div>奇数</div>} //{this.state.n % 2 === 0 && <div>偶数</div>} <button onClick={this.onClick}>+1</button> </> ) }
2.render中循环的写法
Render里面不能直接写for循环,需要用数组,因为for循环没有返回值,所以只会循环一次。
一般会用map实现循环。
循环方法:1' 用变量 2' 用map
1' 用变量 render() { let result = [] for (let i = 0; i < this.state.array.length; i++) { result.push(this.state.array[i]) } return result } 2' 用array.map render() {//给每个元素包个span return this.state.array.map(n => <span key={n}> {n} </span>) }
⚠️注意:所有的循环都需要绑定key
四.生命周期之 componentDidMount
当组件已挂载(已出现在页面)就会执行这个函数
获取当前div的宽度
1' 用id
import React from "react" class App extends React.PureComponent { constructor(props) { super(props) this.state = { n: 1, width: undefined } } onClick = () => { this.setState(state => ({ n: state.n + 1 })) } componentDidMount() { const div = document.getElementById("xxx") const { width } = div.getBoundingClientRect()//析构写法 this.setState({ width }) } render() { return ( <div id="xxx">当前div宽度是:{this.state.width}</div> ) } } export default App
在元素插入页面后执行代码,这些代码依赖DOM。如果你想获取div的高度,就最好在componentDidMount
里写。
2' 除了用id还可以用Ref
React提供了一种更方便的方式来获取div
class App extends React.PureComponent { divRef = undefined //先声明,告诉后来的程序员说,我会动态创建divRef的东西。直接写不利于阅读。 constructor(props) { super(props) this.state = { n: 1, width: undefined } const divRef = React.createRef //第1步.创建引用 } onClick = () => { this.setState(state => ({ n: state.n + 1 })) } componentDidMount() {//第3步.使用this.divRef获取div const div = this.divRef.current const { width } = div.getBoundingClientRect() this.setState({ width }) } render() { return (//第2步.将创建的引用放到div上 <div ref="{this.divRef}">当前div宽度是:{this.state.width}</div> ) } }
好处:不用担心div会冲突,因为用的是内部属性。
ComponentDidMount还可以发起"加载数据的AJAX请求",官方推荐写在这里。比如我要获取当前用户信息,既可以在constructor
里写,也可以在componentDidMount
里写,但是官方推荐写到componentDidMount
。
首次渲染一定会执行此钩子。
ComponentDidMount(){}
没有参数,要获取什么东西只能通过this取。
五.生命周期之 componentDidUpdate()
当组件更新之后就会执行该函数,首次渲染不会执行此钩子。
1.在视图更新后执行代码。首次渲染不会执行此钩子,因为没有更新任何东西。
2.componentDidUpdate()可以发起AJAX请求,用于更新数据经验: 很少在componentDidUpdate发请求,一般都是在componentDidMount里。
3.如果在这个钩子里面再去setState可能会引起无限循环。 因为它们两个会互相调用,除非做一些条件判断。比如说,n是奇数时才会调用componentDidUpdate
4.如果阻止更新UI,componentDidUpdate返回false,则不触发此钩子。
5.参数
componentDidUpdate(prevProps, prevState, snapshot)
六.生命周期之 componentWillUnmount()
当组件将要卸载/死亡时就会执行该函数
1.组件从页面中移除并且从内存里干掉,才会触发并执行componentWillUnmount 移除页面就是从页面跑到内存里面。销毁就是从内存里干掉它。
2.unmount过的组件不能再吃unmount
举例
1.如果你在挂载时监听了window scroll事件,那你应该在组件要死时取消监听,因为如果你不取消那你监听有什么用呢。
2.如果你在挂载时创建了计时器Timer,那么组件要死时就要删掉。
3.如果你在挂载时创建了AJAX请求,那么你就要在componentWillUnmount里取消请求。
所有你发起的那些会长期有效的东西,在你死后你都得取消。但实际上绝大部分的前端都不会做上面的3件事情。因为浪费的只是用户的内存,这就是前端页面越来越占内存的原因,大家都只用内存不清除内存。
总结: componentWillUnmount钩子是用来消除你之前做的一些可能会产生后果的一些事情,比如scroll、Timer、AJAX。
原则: 谁污染谁治理。
分阶段看钩子执行顺序:
React.PureComponent
就不用
React.Component