生命周期与钩子函数(重点)
生命周期指的react实例及组件从创建到运行到销毁的完整的过程。
组件的生命周期可分成三个阶段(状态):
- Mounting:创建阶段:已插入真实 DOM
- Updating:运行阶段:正在被重新渲染
- Unmounting:销毁阶段:已移出真实 DOM
钩子函数指提前埋在生命周期中的函数,等到程序运行到这一刻时,它会自动执行。
常用的钩子函数
componentWillMount、componentWillReceiveProps、shouldComponentUpdate、ComponentDidMount
代码示例:
父组件代码
class Comp1 extends React.Component{
constructor(){
super();
this.state = {
a:1,
isShow:true
}
}
up_click(){
this.setState(state=>({
a:state.a+1
}));
}
un_click(){
this.setState(state=>({
isShow:!state.isShow
}));
}
render(){
return (<div>
<input type="button" onClick={()=>{this.up_click()}} value="更改组件2的属性" />
<input type="button" onClick={()=>{this.un_click()}} value="卸载组件2" />
{this.state.isShow ? <Comp2 b={this.state.a} /> : null}
</div>);
}
}
ReactDOM.render( <Comp1 />, document.getElementById('root'));
子组件代码
class Comp2 extends React.Component{
// 创建阶段
constructor(){
super();
console.log("constructor");
}
componentWillMount(){
console.log("componentWillMount在渲染前调用");
}
render(){
console.log("render");
return <div id='div2'>comp2-{this.props.b}</div>
}
componentDidMount(){
console.log("componentDidMount在第一次渲染后调用");
}
// 运行中阶段
componentWillReceiveProps(newProps) {
console.log(`newProps: ${newProps}
在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。`)
}
shouldComponentUpdate(newProps, newState) {
console.log(`newProps: ${newProps} newState: ${newState}
返回一个布尔值。在组件接收到新的props或者state时被调用。
在初始化时或者使用forceUpdate时不被调用。可以在你确认不需要更新组件时使用。`)
return true; // true表示更新组件;false表示不更新组件
}
componentWillUpdate(nextProps, nextState) {
console.log(`nextProps: ${nextProps} nextState:${nextState}
在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。`);
}
componentDidUpdate(prevProps, prevState) {
console.log(`prevProps:${prevProps} prevState:${prevState}
在组件完成更新后立即调用。在初始化时不会被调用。`)
}
// 销毁阶段
componentWillUnmount() {
console.log('在组件从 DOM 中移除的时候立刻被调用')
}
}
新增的生命周期钩子函数
在 react v16.3 时,新引入了新的生命周期函数:getDerivedStateFromProps,getSnapshotBeforeUpdate。
在未来的 react v17 时,componentWillMount、componentWillReceiveProps、componentWillUpdate 要被废弃。
getDerivedStateFromProps
// 父组件的state发生变化,导致父组件中的当前组件被重新渲染,当前组件的props被修改时,该钩子函数会被触发。
/*componentWillReceiveProps(nextProps){
console.log('nextProps: ', nextProps);
}*/
// 不仅仅具有componentWillReceiveProps的能力,自身组件state变化时,该钩子函数也会被触发。
// 该函数在shouldComponentUpdate之前执行。
// static描述是静态函数,其没有this指向,所以无权操作实例,所以更安全,而且消耗性能低。
// nextProps 传入后的prop数据,即最新的props
// prevState 相对于合并的state来说的前一个状态
static getDerivedStateFromProps(nextProps, prevState) {
console.log('nextProps: ', nextProps);
console.log('prevState: ', prevState);
return {x:nextProps.a} // 合并到当前组件的state
//return null
}
配合 componentDidUpdate 周期函数,getDerivedStateFromProps 是为了替代 componentWillReceiveProps 而出现的。它将原本 componentWillReceiveProps 功能进行划分 —— 更新 state 和 操作/调用 props,很大程度避免了职责不清而导致过多的渲染, 从而影响应该性能。
getSnapshotBeforeUpdate
在 render 之后执行的钩子
// 更新之前
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('getSnapshotBeforeUpdate')
console.log('prevProps:', prevProps)
console.log('prevState:', prevState)
return {x:1}
}
// 更新之后 snapshot能够得到 getSnapshotBeforeUpdate 的返回值
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('componentDidUpdate')
console.log('prevProps:', prevProps)
console.log('prevState:', prevState)
console.log('snapshot:', snapshot)
}
性能优化 shouldComponentUpdate()
决定视图是否需要重新渲染
改变a时,render重新执行;改变b时,render不会重新执行
class App extends Component {
constructor(){
super();
this.state = {
a : 1,
b : 1
}
this.fna = ()=>{
this.setState({ a: new Date().getTime() })
}
this.fnb = function(){
this.setState({ b: new Date().getTime() })
}
}
shouldComponentUpdate(nextProps, nextState){
if( this.state.a !== nextState.a ){
return true;
}else{
return false;
}
}
render(){
return <div>
a: {this.state.a}<br />
b: {this.state.b}<br />
<input type="button" value="改变a" onClick={this.fna} />
<input type="button" value="改变b" onClick={()=>this.fnb()} />
</div>;
}
}
纯组件 PureComponent(浅比较)
pure 是纯的意思,PureComponent 也就是纯组件
浅比较,如果是PureComponent,那么执行add时,视图不会更新;
在修改纯组件中的状态时,检查更新前后的状态是否一致(栈中比较),如果一致,则不更新视图,如果不一致,才更新视图。
而如果是React.Component,那么当执行add时,视图会自动更新。
比较修改状态前后是否一致时,在堆中比较。
class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
a : 1,
arr : ['a','b','c']
}
}
updata(){
this.setState({
a: this.state.a+1
})
}
add(){
this.setState(state=>{
state.arr.push( new Date().toLocaleString() );
return state;
})
}
render() {
return (
<ul>
<li>
<input
type="button"
value={'修改:'+this.state.a}
onClick={this.updata.bind(this)} />
<input
type="button"
value="添加"
onClick={this.add.bind(this)} />
</li>
{ this.state.arr.map((item, ind)=><li key={ind}>{item}</li>) }
</ul>
);
}
}
使用 PureComponent 可能导致不自动更新页面
因为PureComponent是浅比较,所以对数组和对象的更新,如果只是改变了堆中数据,那么系统是不会自动触发render函数的,就不会自动更新了,这个过程在react中被称为数据的突变。
把PureComponent换成Component就不会出现这个问题了,但Component属于深比较,性能消耗的多一些。
不会突变的数据力量
PureComponent浅比较中如何触发render?
只要改变了栈中的数据,视图层就会自动更新。
this.setState(prevState => ({
words: prevState.words.concat(['marklar'])
}));
this.setState(prevState => ({
words: [...prevState.words, 'marklar'],
}));
Object.assign({}, colormap, {right: 'blue'});
以上代码都可以在PureComponent组件中,触发render。
父组件向子组件传递数据(重点)
react 和 vue 和 angular 一样,都是单项数据流,所以项目中推荐的是父向子传递数据。
状态提升
非父子组件数据通信时,把一些共有的数据放到共有的节点中。
爷爷、大伯、父亲、孩子
孩子:修改大伯的状态时,应该把大伯的状态提升到爷爷上,然后由爷爷以属性的形式,把方法先传给父亲,然后父亲以属性的形式把方法传给孩子,孩子触发该方法,就能触发爷爷上的方法,爷爷修改了状态,重新传给大伯,大伯重新渲染页面。
父组件在属性上描述想要传入的数据即可
<abc xyz="123"></abc>
子组件使用 props 接收传入的数据即可
this.props.xyz
子组件向父组件传递数据
父组件:
fn(a, b){
alert(a+b)
}
render(){
return ( <div>
父组件 <br/>
<abc fn={this.fn.bind(this)}></abc>
</div> )
}
子组件:
<button onClick={()=>{ this.props.fn(1,2) }} >按钮</button>
Context 状态树
context 状态树虽然没有被废除,但官方是不建议使用的。
解决的是复杂组件关系时,数据共享的问题,官方建议用eventbus或redux来解决。
Provider 提供者;Consumer 消费者
// 创建名字叫做colorContext的状态树上下文对象,默认值为red。
const colorContext = React.createContext('red');
// 创建外层组件(Provider提供了一些数据共享的能力,表示colorContext这颗状态树对象的值设置为yellow)
// 即,当前组件的后代组件,都可以通过Consumer来使用共享中的数据,即yellow这个数据。
class Container extends Component {
render(){
return <colorContext.Provider value='yellow'>
<div> Container
<Temp2></Temp2>
</div>
</colorContext.Provider>
}
}
// 创建中间层组件(复杂的组件关系时,这可能是很多层,如果使用context,就不需要一层一层的传递props了)
class Temp2 extends Component {
render(){
return <div> temp
<Box></Box>
</div>
}
}
// 创建内层组件(在这层组件中,使用前面提供的数据)
class Box extends Component {
render(){
return <colorContext.Consumer>
{c=><div> box
<div style={{background:c}}>{c}</div>
</div>}
</colorContext.Consumer>
}
}
把 Container 类中的 colorContext 这个标签去掉,直接在 Box 类中用 colorContext 就能够看到默认值了,注意return 后面不能有换行。
HOC 高阶组件
高阶组件(HOC)是react中的高级技术,用来重用组件逻辑。
高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。
// 原始组件
class OldComp extends Component {
render(){
return <div>old</div>
}
}
// 高阶组件
function higherOrderComponent(Comp){
return class extends React.Component {
render(){
return <Comp />;
}
}
}
// 新组件
const NewComp = higherOrderComponent(OldComp);
// App组件的渲染
class App extends Component {
render(){
return <NewComp />;
}
}
Slot 插槽
基本
组合—包含关系—slot
class Component1 extends Component {
render(){
return <div>
{ this.props.children }
</div>;
}
}
class App extends Component {
constructor(){
super();
this.state = {}
}
render(){
return (
<Component1>
<h1>标题</h1>
<p>内容</p>
</Component1>
);
}
}
多个
class Component1 extends Component {
render(){
return <div>
{ this.props.left }
{ this.props.right }
{ this.props.children }
</div>;
}
}
class App extends Component {
constructor(){
super();
this.state = {}
}
render(){
return (
<Component1
left={<div>你好</div>}
right={<div>hello</div>}
>
children仅解析这内容,不会取left和right
</Component1>
);
}
}
Flux 状态管理(已淘汰)
Flux 是比较旧的一种状态管理技术,现在 react 官方已经不推荐使用了。
网上看到的 flux 教程几乎都是2015-2017年的,而当时的 react 和现在的 react 的代码写法上有很大区别。
下面是新代码的写法。
App.js 组件视图层入口
用户打开浏览器后看到的页面,这个页面有2部分功能。
- 从 store 中获取数据,渲染到当前组件的视图层上。
- 点击按钮,触发 action 中的方法,这个方法改变 store 中的数据。(store数据改变后,当前 App 组件重新渲染)
import React, { Component } from 'react';
// 所有的动作
import Actions from './store/actions';
// 仓库,这里保存的是数据和操作数据的方法
import store from './store/store';
// 组件
class App extends Component {
constructor(){
super();
this.state = {
todos : store.todos
}
}
// 执行 action 中的方法
add(){
Actions.add('你好');
}
// 注册一个回调函数,因为 flux 中的数据修改后,不会自动更新视图,
// 所以向 store 中注册一个函数,
// 等 store 中数据发生变化后,要调用这个回调函数,进而修改当前视图。
componentDidMount(){
store.change(()=>{
this.setState({
todos : store.todos
})
})
}
render() {
return (
<div>
<button onClick={()=>{this.add()}}>添加</button>
{ this.state.todos.map(item=><li key={item.id}>
{item.text}
</li>) }
</div>
);
}
}
export default App;
action.js 动作
动作页面,所有操作 flux 的动作都写在此处,比如对 store 中某数据的增删改查操作的动作。
实际上是调用 dispatcher 的 dispatch 方法。
import appDispatcher from './dispatcher';
export default {
add( val ){
appDispatcher.dispatch({type:'ADD', val});
}
}
dispatcher.js 派发
只做一些派发,业务逻辑都写在 store 中
// npm i flux or yarn add flux
import { Dispatcher } from 'flux';
import store from './store';
// 创建 dispatcher
const appDispatcher = new Dispatcher();
// 当用户执行appDispatcher.dispatch时,实际上执行的就是下面注册进来的函数
appDispatcher.register(action => {
// console.log('dispatcher -> action:', action);
switch( action.type ){
case 'ADD':
store.addTodo( action.val );
store.emit('change'); // 触发用户提交过来的回调函数
break;
}
})
export default appDispatcher;
store.js 仓库
仓库,存储数据的容器。
import { EventEmitter } from 'events';
// 创建store对象,让store对象具有on和emit方法(Object.assign是将对象进行合并)
const store = Object.assign({}, EventEmitter.prototype, {
todos : [],
addTodo( val ){
this.todos.push({text:val, id:new Date().getTime()})
},
change( callback ){
this.on('change', callback);
}
});
export default store;
Yarn
yarn 和 npm 一样,都是包管理工具,解决的都是在项目中,对文件的上传、下载、依赖描述等等相关问题。
作用 npm Yarn
安装 npm install(i) yarn
卸载 npm uninstall(un) yarn remove
全局安装 npm install xxx –-global(-g) yarn global add xxx
安装包 npm install xxx –save(-S) yarn add xxx
开发模式安装包 npm install xxx –save-dev(-D) yarn add xxx –dev(-D)
更新 npm update –save yarn upgrade
全局更新 npm update –global yarn global upgrade
卸载 npm uninstall [–save/–save-dev] yarn remove xx
清除缓存 npm cache clean yarn cache clean
重装 rm -rf node_modules && npm install yarn upgrade