睁眼写BUG,闭眼改BUG。

React 基础知识

2020.05.28

Hello React

是什么?

React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”。

为什么?

为什么使用React

ps: 公司用

怎么用?

创建项目

// 创建项目
npx create-react-app my-app
// 进入项目目录,运行项目
npm start

Props(属性)

大多数组件在创建时就可以使用各种参数来进行定制。用于定制的这些参数就称为 props(属性)

  • 标签中使用 {} 拿数据
  • 脚本中使用 this.props.xx 拿数据

State(状态)

React中使用两种数据来控制一个组件: props 和 state。 props 是在父组件中指定,而且一经指定, 在被指定的组件的生命周期中则不在改变。对于需要改变的数据,需要使用 state

  • 一切界面变化都是 状态state变化
  • state 的修改必须通过 setState() 方法
    • this.state.likes = 100; // 直接赋值修改无效
    • setState 是一个 merge 合并操作, 只修改指定属性, 不影响其他属性
    • setState 是异步操作, 修改不会马上生效
    • 每次调用 setState 时,BlinkApp都会重新执行 render 方法重新渲染。

React 组件通信的几种方式

父组件向子组件通信

父组件引入子组件时,在子组件标签上传入对应的值(props),由子组件接收(props)。

childComponent.js

import React from "react";

const Child= (props) => {
    return(
        <h1>
            { props.title }
        </h1>
    )
}

export default Sub;

app.js

import React from 'react';
import Child from './ChildComponent';

function App() {
  return (
    <div className="App">
      <Child title="Hello World!"/>
    </div>
  );
}

export default App;

子组件向父组件通信

利用回调函数,可以实现子组件向父组件通信: 父组件将一个函数作为 props 传递给子组件,子组件调用该回调函数,便可以向父组件通信。

childComponent.js

import React from "react";

const Child = (props) => {

    const clickButton = (msg) => {
        return () => {
            props.callback(msg)
        }
    }

    return(
        <div>
            <h1>
                { props.title }
            </h1>
            <button onClick = { clickButton('我是控制台打印的信息呀!' )} >click button</button>
        </div>
    )
}

export default Child;

app.js

import React,{ Component } from "react";
import Child from './ChildComponent';

export default class App extends Component{
  callback(msg){
      console.log(msg);
  }
  render(){
      return(
        <div className="App">
          <Child title="Hello World!" callback = { this.callback.bind(this) }/>
        </div>
      )
  }
}

跨级组件通信

父组件向子组件的子组件通信,向更深层的子组件通信

有两种方式:

  • 中间组件层层传递 props : 增加复杂度,三层以上最好不用
  • 使用 context 对象 : 相当于一个全局变量,是一个大容器

使用 context 需要满足两个条件:

  • 上级组件要声明自己支持 context, 并提供一个函数来返回相应的 context 对象
  • 子组件要申明自己需要使用 context

childChildComponent.js

import React,{ Component } from "react";
import PropTypes from "prop-types";

export default class ChildChild extends Component{
    // 子组件声明自己需要使用 context
    static contextTypes = {
        color: PropTypes.string,
        callback: PropTypes.func,
    }

    render(){
        const style = { color: this.context.color }
        const clickButtion = (msg) => {
            return () => {
                this.context.callback(msg);
            }
        }
        return(
            <div style = { style }>
                ChildChild<br/>
                <button onClick = { clickButtion("孩子的孩子") }>child button2</button>
            </div>
        );
    }
}

childComponent.js

import React from "react";
import ChildChild from './ChildChildComponent'

const Child = (props) => {

    const clickButton = (msg) => {
        return () => {
            props.callback(msg)
        }
    }

    return(
        <div>
            <h1>
                { props.title }
            </h1>
            <button onClick = { clickButton('我是控制台打印的信息呀!' )} >click button</button>
            <hr/>
            <ChildChild />
        </div>
    )
}

export default Child;

app.js

import React,{ Component } from "react";
import PropTypes from "prop-types";
import Child from './ChildComponent';

export default class App extends Component{

  // 父组件声明自己支持 context
  static childContextTypes = {
    color: PropTypes.string,
    callback: PropTypes.func,
  }

  // 父组件提供一个函数,用来返回相应的 context 对象
  getChildContext(){
    return{
        color: "red",
        callback: this.callback.bind(this)
    }
  }

  callback(msg){
    console.log(msg);
  }

  render(){
    return(
      <div className="App">
        {/* <Child title="Hello World!" callback = { this.callback.bind(this) }/> */}
        <Child title="Hello World!" callback = { this.callback }/>
      </div>
    )
  }
}

如果时父组件向子组件单向通信,可以使用变量,如果子组件想向父组件通信,可以由父组件提供一个回调函数,供子组件调用,回传参数。

在使用 context 时, 需要注意:

  • 父组件要申明自己支持 context,并提供 context 中属性的 PropTypes
  • 子组件也需要申明,提供其所需要的 context 属性的 PropTypes
  • 父组件要提供要给 getChildContext 函数, 以返回一个初始的 context 对象。
  • 如果组件中使用构造函数(constructor), 还需要在构造函数中传入第二个参数 context,并在super 调用父类构造函数传入 context, 否则会造成组件中无法使用 context。
constructor(props, context) {
  super(props, context);
}

改变 context 对象

childChildComponent.js

import React,{ Component } from "react";
import PropTypes from "prop-types";

export default class ChildChild extends Component{
    // 子组件声明自己需要使用 context
    static contextTypes = {
        color: PropTypes.string,
        callback: PropTypes.func,
    }

    render(){
        const style = { color: this.context.color }
        const clickButtion = (msg) => {
            return () => {
                this.context.callback(msg);
            }
        }
        return(
            <div style = { style }>
                ChildChild<br/>
                <button onClick = { clickButtion("blue") }>child button2</button>
            </div>
        );
    }
}

childComponent.js

import React from "react";
import ChildChild from './ChildChildComponent'

const Child = (props) => {

    const clickButton = (msg) => {
        return () => {
            props.callback(msg)
        }
    }

    return(
        <div>
            <h1>
                { props.title }
            </h1>
            <hr/>
            <ChildChild />
        </div>
    )
}

export default Child;

app.js

import React,{ Component } from "react";
import PropTypes from "prop-types";
import Child from './ChildComponent';

export default class App extends Component{

  constructor(props) {
    super(props);
    this.state = {
        color:"red"
    };
  }

  // 父组件声明自己支持 context
  static childContextTypes = {
    color: PropTypes.string,
    callback: PropTypes.func,
  }

  // 父组件提供一个函数,用来返回相应的 context 对象
  getChildContext(){
    return{
        color: this.state.color,
        callback: this.callback.bind(this)
    }
  }

  callback(color){
    // 在Child中无法使用
    // this.setState({
    //   color,
    // })
    console.log(color);
  }

  render(){
    return(
      <div className="App">
        {/* <Child title="Hello World!" callback = { this.callback.bind(this) }/> */}
        <Child title="Hello World!" callback = { this.callback }/>
      </div>
    )
  }
}

非嵌套组件间通信

非嵌套组件, 就是没有任何包含关系的组件,包括兄弟组件以及不在同一个父级中的非兄弟组件。对于非嵌套组件,可以采用下面两种方式:

  • 利用二者共同的父组件的 context 对象进行通信
  • 使用自定义事件的方式

如果采用组件间共同的父级来进行中转,会增加子组件和父组件之间的耦合度,如果组件层次较深的话,找到二者公共的父组件不是一件容易的事,当然还是那句话,也不是不可以...

这里我们采用自定义事件的方式来实现非嵌套组件间的通信。

需要使用一个 events 包:

npm install events --save

新建一个ev.js, 引入 events 包,并向外提供一个事件对象,供通信时使用:

import { EventEmitter } from "events";
export default new EventEmitter();

app.js

import React,{ Component } from "react";
import BOne from './BOneComponent';
import BTwo from './BTwoComponent';



export default class App extends Component{
  render(){
    return(
      <div className="App">
        <BOne></BOne>
        <BTwo></BTwo>
      </div>
    )
  }
}

BOneComponent.js

import React,{ Component } from "react";
import emitter from "./ev"

export default class BOne extends Component{

    constructor(props) {
        super(props);
        this.state = {
            msg:null,
        };
    }
    
    componentDidMount(){
        // 声明一个自定义事件
        // 在组件装载完成以后
        this.eventEmitter = emitter.addListener("callMe",(msg)=>{
            this.setState({
                msg
            })
        });
    }
    
    // 组件销毁前移除事件监听
    componentWillUnmount(){
        emitter.removeListener(this.eventEmitter);
    }

    render(){
        return(
            <div>
                { this.state.msg }
                我是非嵌套 1 号
            </div>
        );
    }
}

BTwoComponent.js

import React,{ Component } from "react";
import emitter from "./ev"

export default class BTwo extends Component{
    render(){
        const cb = (msg) => {
            return () => {
                // 触发自定义事件
                emitter.emit("callMe",msg)
            }
        }
        return(
            <div>
                我是非嵌套 2 号
                <button onClick = { cb("哈哈!我出现了!") }>点击我</button>
            </div>
        );
    }
}

React 的生命周期

三个阶段:加载阶段(Mounting),更新阶段(Update),卸载阶段(UnMounting)

  • constructor: 组件被加载前最先调用,仅调用一次

  • componentwillmount: 组件初始渲染(render())前调用,仅调用一次

  • render: componentwillmount之后,componentDidMount之前, 渲染挂载组件

  • componentDidMount: render之后被调用,仅调用一次

  • shouldComponentUpdate(nextProps,nextState): 组件挂载后(render),接收到新得props和state时调用

  • componentWillUpdate: 在接收到新得props或者state,重新渲染前立刻调用, 在初始化渲染的时候不会被调用

  • componentDidUpdate: 重新渲染后调用,在初始化渲染时该方法不会被调用

  • componentWillUnmount: 组件被卸载前调用

参考

  1. 【简书】柏丘君.React中组件间通信的几种方式
  2. 【React Native中文网】sunnylqm.Props (属性)
  3. 【React Native中文网】sunnylqm.State (状态)
  4. 【React】react.入门教程:认识React

以上文章不分先后