新版react废弃的生命周期替换及分析

676 阅读10分钟

三个生命周期方法在整个声明周期中的位置:

1、自定义函数

事件的首字母大小 onclick ==> onClick onchange ==> onChange

普通的点击事件 ---- 调用事件不加(),加了立即执行

import React, { Component } from 'react';

export default class extends Component {
  constructor (props) {
    super(props);
    this.state = {
    }
  }

  test () {
    console.log('按钮被点击了')
  }

  render () {
    return (
      <div>
        <button onClick={ this.test }>按钮点击事件</button>
      </div>
    )
  }
}

事件对象 --- 自定义事件内有默认的参数 event

import React, { Component } from 'react';

export default class extends Component {
  constructor (props) {
    super(props);
    this.state = {
    }
  }

  test (event) { // ++++++++++++++++++++++++
    console.log('按钮被点击了', event)
  }

  render () {
    return (
      <div>
        <button onClick={ this.test }>按钮点击事件</button>
      </div>
    )
  }
}

如果想要在事件内部使用this --- 构造函数内部添加新的方法

import React, { Component } from 'react';

export default class extends Component {
  constructor (props) {
    super(props);
    // 2.给当前的实例添加一个方法,这个方法其实就是自定义的函数,给自定义的函数绑定this,就可以在自定义的函数内部访问this
    this.testfn = this.test.bind(this); 
    this.state = {
    }
  }

  test () {
    console.log('按钮被点击了', this) // 1.默认this为未定义,需要改变this指向
  }

  render () {
    return (
      <div>
        <button onClick={ this.testfn }>按钮点击事件</button>
      </div>
    )
  }
}

如果想要在事件内部使用this --- 只是改变this指向 --- 推荐

import React, { Component } from 'react';

export default class extends Component {
  constructor (props) {
    super(props);
    this.state = {
    }
  }

  test () {
    console.log('按钮被点击了', this) // 1.默认this为未定义,需要改变this指向
  }

  render () {
    return (
      <div>
      {
        // 2、onClick={ this.test.bind(this) } --- 只是改变this指向
      }
        <button onClick={ this.test.bind(this) }>按钮点击事件</button>
      </div>
    )
  }
}

事件传参

import React, { Component } from 'react';

export default class extends Component {
  constructor (props) {
    super(props);
    this.state = {
    }
  }

  test (str) {
    console.log('按钮被点击了', this) // 1.默认this为未定义,需要改变this指向
    console.log(str)
  }

  render () {
    return (
      <div>
      {
        // 2、this.test.bind(this, '111111')
      }
        <button onClick={ this.test.bind(this, '111111') }>按钮点击事件</button>
      </div>
    )
  }
}

3 生命周期钩子函数

vue: create / mount / update / destroy 稳定版本,部分需要废除的生命周期钩子函数

初始化阶段

constructor () // 初始化数据

componentWillMount () // 老以前(15版本)请求数据,现在基本废除,17版本无用

render () // 必不可少 ---- 初次装载数据

componentDidMount () // 请求数据,DOM操作 --- 等同于vue的mounted

运行时阶段

componentWillReceiveProps () //监听数据,现在基本废除,17版本无用

shouldComponentUpdate () // 默认返回为true --- 提升react性能的关键

componentWillUpdate () // 现在基本废除,17版本无用

render () // 必不可少 ---- 重现渲染视图

componentDidUpdate () // DOM操作 ----- 等同于vue的updated --- 不建议请求数据

销毁阶段

componentWillUnmount () // 组件销毁 --- 类似于vue的beforeDestroy

新版的生命周期

初始化阶段

constructor ()

static getDerivedStateFromProps () // 在初始安装和后续更新上都在调用render方法之前立即调用。它应该返回一个对象以更新状态,或者返回null则不更新任何内容。reactjs.org/docs/react-…

render ()

componentDidMount ()

运行时阶段

static getDerivedStateFromProps ()

shouldComponentUpdate ()

render ()

getSnapshotBeforeUpdate() // 在最近渲染的输出提交到DOM之前立即调用

componentDidUpdate ()

销毁阶段

componentWillUnmount ()

错误处理

static getDerivedStateFromError()

componentDidCatch()

4 修改状态 + 生命周期

this.setState()

import React, { Component } from 'react';
import axios from 'axios';
export default class extends Component {
  constructor (props) {
    super(props);
    this.state = {
      prolist: []
    }
  }
  // +++++++++++++++++++++++++++++
  componentDidMount () {
    // 请求数据
    axios.get('/api/pro').then(res => {
      // 修改状态
      this.setState({
        prolist: res.data.data
      })
    })
  }

  render () {
    let { prolist } = this.state
    return (
      <div>
        {
          prolist.map(item => (
            <p key = { item.proid }> { item.proname } - { item.price } </p>
          ))
        }
      </div>
    )
  }
}

5、运行命令区分webpack的环境

cnpm i cross-env -D

配置package.json的scripts选项

 "scripts": {
    "dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.js",
    "build": "cross-env NODE_ENV=production webpack --config webpack.config.js"
  },

配置webpack的插件plugins

const isDev = process.env.NODE_ENV =='development'

plugins: [ // 添加插件 ---- 数组
  new webpack.optimize.UglifyJsPlugin(), // 压缩js文件
  new webpack.DefinePlugin({ // ++++++++++++++++++
    'process.env': {
      NODE_ENV: isDev ? '"development"' : '"production"'
    }
  }),
  new HtmlWebPackPlugin({ // 实例化一个 html 的webpack的插件
    template: 'index.html' // 找的就是当前文件夹下的index.html文件
  })
],

6、数据请求封装

utils/request.js 自定义axios

// 1、引入axios模块
import axios from 'axios';

// 2、判断是什么环境 -- 开发环境 -- 生产环境
// 真  ----   开发环境 ---- 反向代理
// 假  ----   生产环境
const isDev = process.env.NODE_ENV === 'development'

// 3、自定义axios
let request = axios.create({
  // 基础的请求地址
  baseURL: isDev ? '/api' : 'http://47.92.152.70'
})

// 4、给所有的请求添加头信息
// request.defaults.headers['token'] = localStorage.getItem('token')

// 4、使用axios的拦截器  ----  请求的拦截器  +  响应的拦截器
// http://www.axios-js.com/zh-cn/docs/#%E6%8B%A6%E6%88%AA%E5%99%A8

// 添加请求拦截器
request.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  // 所有的请求都需要的字段,所有的请求添加loading效果
  // token
  config.headers['token'] = localStorage.getItem('token')
  return config;
});

// 添加响应拦截器
request.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  // 消除请求的loading效果
  return response;
});

// 5、暴露axios模块
export default request;

utils/api.js 用来封装请求的接口

// 1、引入自定义的axios
import request from './request';

// 2、封装接口

/**
 * 封装 数据列表的接口
 * pageCode 页面
 * limitNum 每页显示个数
 */
const getProlist = (pageCode, limitNum) => {
  pageCode = pageCode * 1 || 0;
  limitNum = limitNum * 1 || 10;
  // 使用promise解决异步问题
  return new Promise((resolve) => {
    // 因为自定义的axios包含baseUrl,此处只需要写后面的接口即可
    request.get('/pro', { params: { pageCode, limitNum} }).then(res => {
      resolve(res.data)
    })
  })
}

/**
 * 请求轮播图接口
 * type 哪一个类型的轮播图 home / kind / activity 
 */

const getBannerlist = (type) => {
  type = type || 'home';
  return new Promise((resolve) => {
    request.get('/banner', { params: { type }}).then(res => {
      resolve(res.data)
    })
  })
}

// 3、暴露接口
export {
  getProlist,
  getBannerlist
}

7 组件

App.js 为父组件

import React, { Component } from 'react'
import { getBannerlist, getProlist } from '@/utils/api';
import Prolist from './Prolist'; // +++++++++++++++++++++++++++++
export default class extends Component {
  constructor (props) {
    super(props);
    this.state = {
      bannerlist: [],
      prolist: []
    }
  }

  componentDidMount () {
    getBannerlist().then(data => {
      this.setState({
        bannerlist: data.data
      })
    })
    getProlist().then(data => {
      this.setState({
        prolist: data.data
      })
    })
  }

  render () {
    return (
      <div>
        <ul>
          { this.state.bannerlist.map(item => (
            <li key={item.bannerid }>{ item.img }</li>
          ))}
        </ul>
        { 
          // +++++++++++++++++++++++++++++++++++
        }
        <Prolist />
      </div>
    )
  }
}

Prolist.js 为子组件 ------ jsx中的行内样式遵循 js 样式的写法,外加react的{}

import React, { Component } from 'react';

export default class extends Component {
  render () {
    return (
      <ul>
        <li>
          {
            // <img src="" alt="" style="width: 60px;"/>
          }
          <img src="" alt="" style={ { width: '60px'} }/>
          <p>华为meta 30</p>
          <p>5999</p>
        </li>
      </ul>
    )
  }
}

8 父组件给子组件传值

App.js为父组件 Prolist.js 为子组件

父组件给子组件传值 父组件在调用子组件的地方,添加一个自定义的属性,属性的值就是要传递给子组件的值,如果传递的值是变量,boolean,number类型,需要使用到react的{}

<Prolist prolist={ this.state.prolist }/> 在子组件中,可以通过this.props访问到父组件传递给子组件的数据,在哪里拿数据取决于哪里使用数据,子组件渲染数据

import React, { Component } from 'react';

export default class extends Component {
  render () {
    console.log(this.props)
    return (
      <ul>
        {
          this.props.prolist.map(item => (
            <li key={item.proid}>
              <img src={  item.proimg  } alt="" style={ { width: '60px'} }/>
              <p>{ item.proname }</p>
              <p>{ item.price }</p>
            </li>
          ))
        }
      </ul>
    )
  }
}

如果需要验证父组件传递的数据的数据类型

react15.5之后类型的校验在第三方模块 prop-types中

cnpm i prop-types -S

子组件中验证组件中的数据类型

bool, array, func, number, object, string symbol, node, element

import React, { Component } from 'react';
import PropTypes from 'prop-types'; // ++++++++++++++++++++++
class Com extends Component {
  render () {
    console.log(this.props)
    return (
      <ul>
        {
          /*
          <li>
          {
            // <img src="" alt="" style="width: 60px;"/>
          }
          <img src="" alt="" style={ { width: '60px'} }/>
          <p>华为meta 30</p>
          <p>5999</p>
        </li>
        */
        }
        {
          this.props.prolist.map(item => (
            <li key={item.proid}>
              <img src={  item.proimg  } alt="" style={ { width: '60px'} }/>
              <p>{ item.proname }</p>
              <p>{ item.price }</p>
            </li>
          ))
        }
      </ul>
    )
  }
}
// +++++++++++++++++++
Com.propTypes = {
  prolist: PropTypes.array
}

export default Com

9、子组件给父组件传值

子组件给父组件传值,实际上就是父组件给子组件传递了自定义的属性,这个属性的值是一个函数,父组件定义这个函数,子组件调用这个函数,一定要记得this指向问题

子组件

import React, { Component } from 'react';

// class Com extends Component {
//   sendData () { // ++++++++++++++
//     console.log(this.props) // 记得改变this指向,{fn: function()}
//     this.props.fn('我是子组件')
//   }

//   render () {
//     return (
//       <div>
//         <button onClick={ this.sendData.bind(this) }>给父组件传值</button>
//       </div>
//     )
//   }
// }

class Com extends Component {
  render () {
    return (
      <div>
        <button onClick={ () => {
          // +++++++++++++++++++++++
          this.props.fn('222222222222')
        } }>给父组件传值</button>
      </div>
    )
  }
}

export default Com;

父组件

import React, { Component } from 'react'
import Child from './Child';
export default class extends Component {
  constructor (props) {
    super(props);
    this.state = {
    }
  }

  getData (str) { // +++++++++++++++++++++++++++
    console.log(str)
  }

  render () {
    return (
      <div>
        {
          // ++++++++++++++++++++++ fn 
        }
        <Child fn={ this.getData.bind(this) }/>
      </div>
    )
  }
}

10、react的脚手架

create-react-app

1、安装脚手架

cnpm i create-react-app -g

create-react-app myapp

2、 不安装脚手架 使用 npx ---- node 8.9以上自动就有

npx create-react-app myapp (用这个创建项目)

3、 使用dva 创建react项目 --- dva/cli

cnpm install dva-cli -g

dva new myapp

长风破浪会有时,直挂云帆济

一、componentDidMount

react 文档中有建议将网络请求放在 componentDidMount 而不是放在 componentWllMount 中,文档的阐述是说只有放在 componentDidMount 中,才能当数据拿到之后,调用 setState 去更新组件,但其实在 componentWillMount 中进行异步操作,也能进行 setState。(有另一种解释,reactv16中使用了 fiber 架构,只有 componentDidMount 能够保证只执行一次,componentWillMount 无法保证):

reactjs.org/docs/faq-aj… 这里主要是对 componentDidMount 的文档上的再次理解。

componentDidMount 的作用时机实际上在讲组件(插入树中)之后立即调用,如果需要对 DOM 节点进行初始化,应该在这里进行操作。同样的,如果需要在远程节点请求数据,也应该在 componentDidMount 中进行。

文档中也推荐 componentDidMount 中进行 subscription,比如 setTimeout 这种,当然需要在 componentWillMount 中销毁。

在 componentDidMount 中是能够立即进行 setState 的(这里的立即进行 setState 是指给 state 执行初始值的操作,而不是网络请求),这个过程是发生在浏览器更新前, 会触发一次额外的 render(),如果此时你在 render 中输出内容,会多输出一次。虽然会多触发一次 render,但其实用户是看不到的中间状态的。但是这种模式并不应当使用,会导致性能问题,如果要初始化 state, 应当在 constructor 中进行。当然,如果需要显示 modal 或者是 tooltip 这种提示型的组件,并且位置需要依赖 DOM,也可以在 componentDidMount 中进行处理。

二、componentDidUpdate

componentDidUpdate(prevProps, prevState, snapshot) 在组件发生更新之后会立即被调用,但是在初始化的时候是不会被调用的。

因此在 componentDidUpdate 中可以在组件更新的时候,对 DOM 进行一些操作,同时这个方法有三个参数,可以通过参数比较比如 props ,能够判断是否需要网络请求新的数据等,比如官方文档给的示例如下:

componentDidUpdate(prevProps) {
  // Typical usage (don't forget to compare props):
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);
  }
}

如果你在 componentDidUpdate中立即执行了 setState,需要额外注意的是你可能引入了死循环,这是因此每次 setState 都会执行到 componentDidUpdate,然后又进行 setState,从而导致整个应用挂掉。如果真的需要 setState,是必须放在条件中的,比如上面的示例代码中,由条件的决定,则不会引发死循环问题。

当然,这种做法也可能会导致重复的进行 render,虽然用户看不见,但是非常影响组建的性能

如果你进行了 state的派生操作 很可能会引发一系列的问题,导致应用无法按照预期执行。更多关于派生 state 的问题,官方博客见:

reactjs.org/blog/2018/0… 之后会将这篇文章翻译一下,深度的理解。

注意: 如果 shouldComponentUpdate 返回了 false, componentDidUpdate 是一定不会被执行的

三、componentWillUnmount

react 在卸载和销毁组件之前会立即调用 componentWillUnmount 方法, 如果使用计时器比如 setTimeout,可以在此时进行销毁。

初次之外可以在该方法中取消网络请求,或者是清楚 componentDidMount 中的一些监听订阅行为。

componentWillUnmount 中是不能进行 setState,因为组件已经被销毁了,所以永远不会在去重新 render。