1. 两种创建class组件的方式
- ES5写法(已经过时了)
import React from 'react'
const A = React.createClass({
render(){
return (
<div>hi</div>
)
}
})
export default A
- ES6 最新的写法
import React from 'react'
class B extends React.Component {
constructor(props){
super(props);
}
render(){
return (
<div>hi</div>
)
}
}
export default B
2. props 外部数据
props的作用
接受外部数据
只能读不能写
外部数据由父组件传递
接受外部函数
在恰当的时机,调用该函数
该函数一般是父组件的函数
传入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是一个回调
B组件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 获取外部数据
// 要么不初始化,即不写constructor
// 要么初始化,且必须写全套(不写super直接报错)
这样,this.props 就是外部数据对象的地址了
不推荐子组件对外部 props 进行修改!
如何写 props 值
- 原则上应该由数据的主人进行更改
3. 组件的相关钩子
-
componentWillReceiveProps钩子
- 当组件接受新的props时,会触发此钩子
- 该钩子已经被弃用
- 更名为UNSAFE_componentWillReceiveProps
- 总而言之,不要使用这个钩子
componentWillReceiveProps(newProps){ // 参数为新的props console.log('旧的 props 为') console.log(this.props) console.log('新的 props 为') console.log(newProps) // 注意 console 的延迟计算 bug }
4. state & setState 内部数据
class B extends React.Component {
constructor(props) {
super(props);
this.state = {
user: {name:'frank',age:18}
}
}
render(){/* ... */}
}
- 读数据 this.state
this.state.xxx.yyy.zzz
- 写数据 this.setState(???,fn)
this.setState(newState,fn)
// 注意setState不会立刻改变this.state,会在当前代码运行完后,再去更新this.state,从而触发U更新
// fn函数会在写入成功时执行
this.setState(()=>newState,fn)
// 这种方式的state反而更易于理解
// fn函数会在写入成功时执行
类组件setState 会自动将新state与旧state进行第一层级数据合并
5. React生命周期
我们类比如下的代码来理解生命周期
let div = document.createElement('div')
// 这是div的create / construct过程
div.textContent = 'hi'
// 这是初始化state
document.body.appendChild(div)
// 这是div的mount过程
div.textContent = 'hi2'
// 这是div的update过程
div.remove()
// 这是div的unmount过程
同理:React组件也有这些过程,我们称之为生命周期
生命周期函数列表概览
- constructor() 创建之后 可以来初始化 state
- static getDerivedStateFromProps() 不常用
- shouldComponentUpdate() 该更新组件吗? 返回布尔值 return false 阻止更新
- render() 渲染了 创建虚拟DOM
- getSnapshotBeforeUpdate() 不常用
- componentDidMount() 组件已经挂载后
- componentDidUpdate() 组件已经更新后
- componentWillUnmount() 组件将要卸载时
- static getDerivedStateFromError() 不常用
- componentDidCatch() 不常用
- constructor() 创建之后
初始化 props ,state,但此时不能调用 setState ,用来写 bind this
constructor(){
/* 其他代码略写 */
this.onClick = this.onClick.bind(this)
}
// 可以用虚新语法代替
onClik = () => {}
constructor(){ /* 其他代码略写 */ }
- shouldComponentUpdate() 是否更新UI
返回 true 表示不阻止 UI 更新,返回 false 表示阻止 UI 更新
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(nextProps, nextState) { // 判断 n值变化与否 是否该更新UI
if (nextState.n === this.state.n) {
return false;
} else {
return true;
}
}
render() {
console.log('渲染了')
return (
<div className='App'>
<div>
{this.state.n}
<button onClick={this.onClick}>+1</button>
</div>
</div>
)
}
}
面试常问:shouldComponentUpdate有什么用?
答:它允许我们手动判断是否要进行组件更新,我们可以根据应用场景灵活地设置返回值,以避免不必要的更新
这个例子理解后我们会发现其实可以将newState和 this.state的每个属性都对比一下如果全都相等,就不更新如果有一个不等,就更新,确实,React也内置了这个功能
React.PureComponent (纯组件 ) 可以代替 React.Component
class App extends React.PureComponent { // 只需改这一行便有了 上面的功能
PureComponent 会在 render 之前对比新 state 和旧 state 的每一个 key,以及新 props 和旧 props 的每一个 key。
如果所有 key 的值全都一样,就不会 render;如果有任何一个 key 的值不同,就会 render。
- render 渲染组件
用来展示视图
return (<div> ... </div>)
- 只能有一个根元素
- 如果有两个根元素,就要用<React.Fragment>包起
- <React.Fragment/>可以缩写成<></>
技巧:
- render 里面可以写if...else
- render 里面可以写?:表达式
- render 里面不能直接写for 循环,需要用数组
- render 里面可以写array.map(循环)
render() {
return this.state.array.map(n => <span key={n}>{n}</span>)
}
// 必须要有key
- componentDidMount() 组件已经挂载了
- 在元素插入页面后执行代码,这些代码依赖DOM
- 比如你想获取div的高度,就最好在这里写
- 此处可以发起加载数据的AJAX请求(官方推荐)
- 首次渲染会执行此钩子
componentDidMount(){
const div = document.getElementById('xxx')
const {width} = div.getBoundingClientRect() // 获取宽度
this.setState({width})
} // 挂载后获取元素的宽度
可以使用使用 Creating refs 代替 document.getElementById
class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
n: 1
}
this.divRef = React.createRef() // 声明引用
}
onClick = () => {
this.setState((state) => ({n: state.n + 1}))
this.setState((state) => ({n: state.n - 1}))
}
componentDidMount() {
const div = this.divRef.current // 使用引用
console.log(div)
}
render() {
console.log('渲染了')
return (
<div className='App' ref={this.divRef}> // 添加引用属性
<div>
{this.state.n}
<button onClick={this.onClick}>+1</button>
</div>
</div>
)
}
}
- componentDisUpdate() 组件更新之后
- 在视图更新后执行代码
- 此处也可以发起AJAX请求,用于更新数据(看文档)
- 首次渲染不会执行此钩子
- 在此处setState可能会引起无限循环,除非放在if 里
- 若shouldComponentUpdate返回false,则不触发此钩子
- componentWillUnmount 组件将要卸载时
- 组件将要被移出页面然后被销毁时执行代码
- unmount过的组件不会再次mount
举例子:
- 如果你在c..DidMount里面监听了window scroll
- 那么你就要在c..WillUnmount 里面取消监听
- 如果你在c..DidMount里面创建了Timer
- 那么你就要在c..WillUnmount 里面取消Timer
- 如果你在c..DidMount里面创建了AJAX请求
- 那么你就要在c..WillUnmount里面取消请求
- 原则:谁污染谁治理