react组件进阶,传值等生命周期的使用

117 阅读12分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

React组件进阶

  • children属性
  • props校验
  • props默认值
  • 生命周期与钩子函数

props校验-基本使用

目标

校验接收的props的数据类型,增加组件的健壮性

类型校验的必要性

对于子组件来说,props是外来的,无法保证组件使用者传入什么格式的数据,有了类型校验,我们的程序就更加健壮了。

步骤

  1. 导入 prop-types 包 。这个包在脚手架创建项目时就自带了,无须额外安装

    import PropTypes from 'prop-types' 这里的PropTypes可以改成其他的名字

  2. 使用组件名.propTypes = {属性名1: 类型1, ...} 来给组件的props添加校验规则

    这里的propTypes是固定写法

示例

import PropTypes from 'prop-types'
class App extends React.component {
  render(){
    return (
        <h1>Hi, {props.colors}</h1>
    )
  }
    
}
App.propTypes = {
    // 约定colors属性为array类型
    // 如果类型不对,则报出明确错误,便于分析错误原因
    colors: PropTypes.array
}

作用:规定接收的props的类型必须为数组,如果不是数组就会报错,报错的格式如下:

image.png

小结

  1. 为啥要对props进行校验?

  2. 步骤是:

    1. 导入 __ _包
    2. 设置格式: __ ___

props校验-常见规则

目标

了解react组件props校验的常见规则

内容:

  1. 常见类型:array、bool、func、number、object、string
  2. React元素类型:element
  3. 必填项:isRequired
  4. 特定结构的对象:shape({ })

示例

Com.propTypes = {
  fn: PropTypes.func, // fn 是函数类型
  isEdit: PropTypes:bool.isRequired // isEdit的类型是bool,且必须要传入
  
  // 特定结构的对象
  goodInfo: PropTypes.shape({ // goodInfo的格式一个对象,有 数值price属性 和 字符串name属性
    price: PropTypes.number,
    name: PropTypes.string
  })
}

props默认值(了解)

目标

掌握给组件的props提供默认值的两种方法

默认值

定义: 没有赋值的情况下,给属性赋一个值

好处:简化调用组件时要传入的属性,更方便用户使用

两种方式

两种方式:

  1. defaultProps
  2. 解构赋值的默认值

方法1:defaultProps

通过defaultProps可以给组件的props设置默认值,在未传入props的时候生效

定义组件

class App extends React.Component{
    return (
        <div>
            此处展示props的默认值:{ this.props.pageSize }
        </div>
    )
}
// 设置默认值
App.defaultProps = {
    pageSize: 10
}
// 类型限制
App.propTypes = {
  pageSize: PropTypes.number
}

使用组件

// 不传入pageSize属性
<App />

方法2:解构赋值的默认值

class App extends React.Component{
    return (
     // 解构赋值的默认值
     const { pageSize = 10} = this.props
        <div>
            此处展示props的默认值:{ this.props.pageSize }
        </div>
    )
}
// 类型限制
App.propTypes = {
  pageSize: PropTypes.number
}

小结

props校验和默认值简化-类的静态成员-static(了解)

目标

了解静态属性的概念和特点,能用它来简化props校验和默认值

什么是静态成员

  • 静态成员:通过类或者构造函数本身才能访问的属性或者方法

  • 实例成员: 通过实例调用的属性或者方法

    示例

    class Person {
      constructor(name){
        this.name = name // 实例成员
      }
      static n = 1 // 静态成员 有static修饰
    }
    

静态成员的定义和访问

定义

class Person {
  constructor(name){
    this.name = name // 实例成员
  }
  // 方法1:在class内部 通过static来定义
  static n = 1 // 静态成员 有static修饰
}
// 方法2 在class外部 通过类型.属性名来定义
Person.m = function() { console.log()}

访问

Person.n
Person.m()

简化props校验和默认值

把校验规则和默认值放入class内部

组件生命周期-概述

目标

能够理解什么是组件的生命周期

生命周期

生命周期:一个事物从创建到最后消亡经历的整个过程

组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程

意义:组件的生命周期有助于理解组件的运行方式、完成更复杂的组件功能、分析组件错误原因等

钩子函数

在生命周期的不同阶段,会自动被调用执行的函数,为开发人员在不同阶段操作组件提供了时机。

注意:只有类组件 才有生命周期钩子函数

image.png

react类组件的生命周期钩子函数-整体说明

projects.wojtekmaj.pl/react-lifec…

image.png

小结

  1. __ _组件有生命周期,****组件没有生命周期的概念
  2. 类组件的生命周期有__个阶段, 5个钩子函数

组件生命周期-挂载阶段

目标

能够说出组件的挂载阶段的钩子函数以及执行时机

内容

  • 执行时机:组件创建时(页面加载时)
  • 执行顺序:constructor() -> render() -> componentDidMount()
钩子 函数触发时机作用
constructor创建组件时,最先执行1. 初始化state 2. 创建Ref等
render每次组件渲染都会触发渲染UI
componentDidMount组件挂载(完成DOM渲染)后1. 发送网络请求 2.DOM操作

小结

  1. 挂载阶段有****个钩子? 分别是? 各个钩子函数中可以适合做什么事?

组件生命周期-更新阶段

目标

能够说出组件的更新阶段的钩子函数以及执行时机

更新阶段的钩子

更新阶段会执行两个钩子: render() -> componentDidUpdate()

三种操作可触发组件更新

  1. 调用setState。它能改数据&& 更新页面
  2. 调用forceUpdate()
  3. 组件接收到新的props

说明:以上三者任意一种发生,组件就会进入更新阶段

更新阶段能做的事情

钩子函数触发时机作用
render每次组件渲染都会触发渲染UI(与 挂载阶段 是同一个render)
componentDidUpdate组件更新(完成DOM渲染)后DOM操作,可以获取到更新后的DOM内容

如果有些事情是需要每次更新去做的,就可以写在componentDidUpdate中。例如:数据本地存储

小结

  1. 更新阶段有 __ __ __ __ 个钩子,分别可以做什么事情?

组件生命周期-卸载阶段

目标

能够说出组件的销毁阶段的钩子函数以及执行时机

内容

执行时机:组件销毁

钩子函数触发时机作用
componentWillUnmount组件卸载(从页面中消失)执行清理工作(比如:清理定时器等)

示例:清理定时器&&移除事件

/* eslint-disable react/prop-types */
import React, { Component, createRef } from 'react'
import ReactDOM from 'react-dom'class Son extends Component {
  constructor () {
    super()
​
    this.timer = setInterval(() => {
      console.log('子组件:', Date.now())
    }, 1000)
    this.fn = () => {
      console.log('鼠标移动...')
    }
    window.addEventListener('mousemove', this.fn)
  }
​
  render () {
    return <div>子组件:{this.props.content}</div>
  }
​
  componentDidUpdate () {
    console.log('子组件: componentDidUpdate')
  }
​
  componentWillUnmount () {
    console.log('子组件 卸载: componentWillUnmount')
    // 删除事件
    window.removeEventListener('mousemove', this.fn)
    // 删除定时器
    clearInterval(this.timer)
  }
}
​
export default class App extends Component {
  constructor () {
    super()
    console.log('1. constructor')
    this.state = {
      content: '',
      isShow: true
    }
    this.refTxt = createRef()
  }
​
  hChange = (e) => {
    this.setState({ content: e.target.value })
  }
​
  click = () => {
    this.forceUpdate()
  }
​
  render () {
    console.log('2. render')
    return (
      <div>
        <button onClick={this.click}>更新</button>
        组件<input value={this.state.content} onChange={this.hChange} />
        <br />
        {this.state.isShow && <Son content={this.state.content} />}
      </div>
    )
  }
​
  componentDidMount () {
    console.log('3. componentDidMount')
    console.log(this.refTxt.current)
​
    // axios.get()
  }
​
  componentDidUpdate () {
    console.log('更新完成: componentDidUpdate')
  }
}
​
ReactDOM.render(<App />, document.getElementById('root'))

综合案例-B站评论列表案例

本地持久化的功能

核心代码

componentDidUpdate() {
  localStorage.setItem('list', JSON.stringify(this.state.list))
}
componentDidMount() {
  const list = JSON.parse(localStorage.getItem('list')) || []
  this.setState({ list })
}

setState进阶-掌握它的3个特点

目标

掌握setState的三个特点

问题导入

export default class App extends Component {
​
  state = {
     n: 1
  }
​
  hClick = () => {
    this.setState({n: 100})
    console.log(this.state.n)
    console.log(document.getElementById('span').innerHTML) // 这会打印?
  }
​
  render () {
    return (
      <div>
        <button onClick={this.hClick}>click</button>
        <span id="span">
          n: {this.state.n}
        </span>
      </div>
    )
  }
}

setState的特点

  1. 可以表现为异步。

    setState调用之后,并不会立即去修改state的值,也不会立即去更新dom

  2. 多次调用会合并。setState({对象})会合并,再统一触发一次render()

  3. 使用不当会死循环。在componentDidUpdate, render 中调用setState()会导致死循环

小结

setState进阶-第二个参数

目标

能够掌握setState的完整语法格式

setState的第二个参数

格式1

this.setState({} [,回调函数])

回调函数是可选的,它的作用是:当本轮setState生效(state更新,页面ui更新)之后,会调用回调函数

格式2

this.setState((上一状态) => {
  return 新状态
}[,回调函数])

回调函数是可选的,它的作用是:当本轮setState生效(state更新,页面ui更新)之后,会调用回调函数

示例1

  state = {
    n: 1
  }
​
  hClick = () => {
    this.setState( preState => ({ n: preState.n + 1 }))
    this.setState( preState => ({ n: preState.n + 2 }))
    this.setState( preState => ({ n: preState.n + 3 }))
  }
  // 页面上的n会是多少?

state可以获取到叠加的状态,适用于需要调用多次setState

示例2

  state = {
    n: 1
  }
​
  hClick = () => {
    this.setState( preState => ({ n: preState.n + 1 }), ()=>{console.log(this.state.n)})
    this.setState( preState => ({ n: preState.n + 2 }), ()=>{console.log(this.state.n)})
    this.setState( preState => ({ n: preState.n + 3 }), ()=>{console.log(this.state.n)})
  }
  // 1. 页面上的n会是多少?
  // 2. 三个console.log会输出什么?

state可以获取到最新的状态,适用于获取state更新之后的状态。

setState练习

任务说明

初始初始隐藏textarea。

点击回复按钮之后:

  1. 显示textArea
  2. 让它获取焦点

import React, { Component } from 'react'
import ReactDOM from 'react-dom'export default class App extends Component {
  state = {
    isReplay: false
  }
​
  hClick = () => {
    // 你的代码
  }
​
  render () {
    return (
      <div>
        <p>要求:初始隐藏textarea。点击回复按钮,显示textArea,并让它获取焦点</p>
        <button onClick={this.hClick}>回复</button>
        <textarea />
      </div>
    )
  }
}
​
ReactDOM.render(<App />, document.getElementById('root'))
​

完成:

import ReactDOM from 'react-dom'
import React, { Component, createRef } from 'react'export default class App extends Component {
  constructor () {
    super()
    this.txtRef = createRef()
  }
​
  state = {
    isShow: false
  }
​
  hClick = () => {
    this.setState({ isShow: true }, () => {
      // 获取焦点
      this.txtRef.current.focus()
    })
  }
​
  render () {
    return (
      <div>
        <button onClick={this.hClick}>回复</button>
        {this.state.isShow && <textarea ref={this.txtRef} />}
      </div>
    )
  }
}
​
ReactDOM.render(<App />, document.getElementById('root'))
​

setState进阶-同步or异步

目标

能够说出setState到底是同步的还是异步

内容

setState本身并不是一个异步(setTime, setInterval, ajax,Promise.then.....)方法,其之所以会表现出一种异步的形式,是因为react框架本身的性能优化机制

在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用 setState 不会同步更新 this.state,除此之外的setState调用会同步执行this.state。

所谓“除此之外”,指的是绕过React通过 addEventListener 直接添加的事件处理函数,还有通过setTimeout || setInterval 产生的异步调用。

简单一点说:

  1. 经过React 处理(事件回调,钩子函数)中的setState是异步更新
  2. 没有经过React处理(通过 addEventListener || setTimeout/setInterval)中的setState是同步更新

示例代码

import reactDom from 'react-dom'import React, { Component } from 'react'class App extends Component {
  state = {
    n: 1
  }
​
  // setTimeout(() => {
  //   this.setState({ n: 2 })
  //   console.log(this.state.n)
  //   console.log(document.getElementById('btn').innerHTML)
  // }, 2000)
​
  componentDidMount () {
    // this.setState({ n: 2 })
    // console.log(this.state.n)
    // console.log(document.getElementById('btn').innerHTML)
​
    document.getElementById('btn').addEventListener('click', () => {
      this.setState({ n: 2 })
      console.log(this.state.n)
      console.log(document.getElementById('btn').innerHTML)
    })
  }
​
  click = () => {
    this.setState({ n: 2 })
    console.log(this.state.n)
    console.log(document.getElementById('btn').innerHTML)
  }
​
  render () {
    return (
      <div>
        {/* <button id="btn" onClick={this.click}> */}
        <button id="btn" onClick={this.click}>
          {this.state.n}
        </button>
      </div>
    )
  }
}
​
reactDom.render(<App />, document.getElementById('root'))
​

总结

setState是同步的方法,但是react为了性能优化,所以setState在react的事件中表现得像异步。

参考链接:

json-server-安装及基本使用

参考:教程

官网: npm

作用

能json-server快速地根据已有json文件,生成接口

json ----json-server-----> 接口

使用步骤

全局安装json-server

以前安装过的全局包:nodemon,nrm

它是依赖于nodejs的第三方包,它是一个独立的工具,并不限于某个项目,可以全局安装。

npm i json-server -g

如果是mac本,可能需要加sudo,即:sudo npm i json-server -g

准备空文件夹

在任意目录下,准备一个空文件夹,取名mock-server(可改成其它名字)

创建json文件

在文件夹中新建一个名为db.json文件(可改其它名称,注意名字是是英文

初始化结构

在db.json文件中,按json格式的要求,去定义一个对象:

  • 键值对格式
  • 双引号括起来
{
  "assets": [
        { "id": 1, "name": "外套", "price": 99 },
        { "id": 2, "name": "裤子", "price": 34 },
        { "id": 3, "name": "鞋", "price": 25.4 },
        { "id": 4, "name": "头发", "price": 19900 }
    ]
}

启动接口服务

根据上面创建的db.json自动地生成接口。

进入到上一步创建的文件夹下,

此文件夹下,打开命令行窗口,输入命令json-server db.json -p 8888 (json-server后有空格)

如果没有错误,则运行结果如下:

测试

在浏览器中访问上面地址

注意:

  • 产生的接口地址中的assets是与db.json中的属性名是对应的。
  • db.json的内容必须是json格式。
  • 属性名 ---> 接口的地址
  • 属性值 ---> 接口返回数据 (数组|对象)

原理图

注意

  • 小黑窗不要关!
  • 小黑窗上不要用鼠标选中任何内容!

RESTful接口

目标

了解restFul风格的定义

问题导入

在写接口时,每个程序员都有自己的写法:取不同的名字,例如:实现添加资产

A同学: localhost:3000/addAsset | delAsset

B同学: localhost:3000/insertAsset | deleteAsset

C同学: localhost:3000/tjzj | sczj

RESTful接口

针对上述问题,提出一套约定。RESTful接口的规范是通过:

  • 请求方式来决定操作类别(添加,删除,修改,查询)
  • 用名词来表示要操作的目标

json-server提供的就是符合restful规则的接口。

测试

以上面启动的服务为例:一旦服务启动成功,就会自动生成如下接口地址。

接口地址请求方式操作类型
localhost:3000/assetsGET获取全部数据
localhost:3000/assets/1GET获取单个数据
localhost:3000/assetsPOST添加操作 。参数: {name,price}
localhost:3000/assets/1DELETE删除操作
localhost:3000/assets/1PUT完整修改。参数:{name,price}
localhost:3000/assets/1PATCH局部修改。参数:{name}

以上接口就是符合restful要求的接口规则。

案例-todos-准备接口

目标

用json-server实现接口

步骤

  • 准备文件data.json
{
  "todos": [
    {
      "id": 1,
      "name": "读书",
      "done": false
    },
    {
      "name": "写字",
      "done": true,
      "id": 4
    }
  ]
}
  • 使用json-server启动接口
json-server data.json --port 8888
  • 接口地址
http://localhost:8888/todos
​
增加: post
删除: delete
修改: patch  / put
获取: get

案例-todos-引入素材拆分组件

目标

引入基本素材,并拆分组件

分析

目录结构

|-styles
|-------base.css
|-------index.css
|-components
|-------Footer.js
|-------Header.js
|-------Main.js
|-index.js

案例-todos-具体功能

index.js

import React, { Component } from 'react'
import Footer from './component/Footer'
import Main from './component/Main'
import Header from './component/Header'
import axios from 'axios'
export default class App extends Component {
  state = {
    list: []
  }
​
  componentDidMount () {
    this.getTodos()
  }
​
  getTodos = async () => {
    const res = await axios.get('http://localhost:8888/todos')
    this.setState({
      list: res.data
    })
  }
​
  add = async (content) => {
    await axios({
      url: 'http://localhost:8888/todos',
      method: 'post',
      data: {
        content,
        isDone: false
      }
    })
    this.getTodos()
  }
​
  del = async (id) => {
    await axios({
      url: 'http://localhost:8888/todos/' + id,
      method: 'delete'
    })
    this.getTodos()
  }
​
  update = async (id, isDone) => {
    await axios({
      url: 'http://localhost:8888/todos/' + id,
      method: 'patch',
      data: {
        isDone
      }
    })
    this.getTodos()
  }
​
  render () {
    const { list } = this.state
    return (
      <section className="todoapp">
        <Header add={this.add} />
        <Main update={this.update} list={list} del={this.del} />
        <Footer />
      </section>
    )
  }
}
​

main.js

import React, { Component } from 'react'
import PropTypes from 'prop-types'
export default class Main extends Component {
  render () {
    const { list } = this.props
    return (
      <section className="main">
        <input id="toggle-all" className="toggle-all" type="checkbox" />
        <label htmlFor="toggle-all">Mark all as complete</label>
        <ul className="todo-list">
          {list.map((it) => (
            <li key={it.id} className={it.isDone ? 'completed' : ''}>
              <div className="view">
                <input
                  className="toggle"
                  onChange={() => {
                    this.props.update(it.id, !it.isDone)
                  }}
                  type="checkbox"
                  checked={it.isDone}
                />
                <label>{it.content}</label>
                <button
                  onClick={() => this.props.del(it.id)}
                  className="destroy"
                />
              </div>
              <input className="edit" value="Create a TodoMVC template" />
            </li>
          ))}
​
          {/* <li>
            <div className="view">
              <input className="toggle" type="checkbox" />
              <label>Buy a unicorn</label>
              <button className="destroy" />
            </div>
            <input className="edit" value="Rule the web" />
          </li> */}
        </ul>
      </section>
    )
  }
}
Main.propTypes = {
  list: PropTypes.array.isRequired,
  del: PropTypes.func.isRequired,
  update: PropTypes.func.isRequired
}
​

Header.js

import React, { Component } from 'react'
import PropTypes from 'prop-types'export default class Header extends Component {
  state = { content: '1' }
  render () {
    return (
      <header className="header">
        <h1>todos</h1>
        <input
          onKeyUp={this.add}
          value={this.state.content}
          onChange={({ target }) => {
            console.log(target)
            this.setState({ content: target.value.trim() })
          }}
          className="new-todo"
          placeholder="What needs to be done?"
          autoFocus
        />
      </header>
    )
  }
​
  add = async (e) => {
    if (e.code === 'Enter') {
      await this.props.add(this.state.content)
​
      this.setState({ content: '' })
    }
  }
}
​
Header.propTypes = {
  add: PropTypes.func.isRequired
}
​

Footer.js

import React, { Component } from 'react'export default class Footer extends Component {
  render () {
    return (
      <footer className="footer">
        <span className="todo-count">
          <strong>0</strong> item left
        </span>
        <ul className="filters">
          <li>
            <a className="selected" href="#/">
              All
            </a>
          </li>
          <li>
            <a href="#/active">Active</a>
          </li>
          <li>
            <a href="#/completed">Completed</a>
          </li>
        </ul>
        <button className="clear-completed">Clear completed</button>
      </footer>
    )
  }
}
​

列表渲染功能

核心步骤

  1. App组件中通过axios发送请求获取到列表
  2. 通过父传子把list传递给TodoMain组件
  3. TodoMain组件渲染任务列表