React 进阶 - Component 组件

2023-05-17 20:43:23 浏览数 (2)

# 一切皆组件

在 React 世界里,一切皆组件, React 项目全部起源于组件。组件可以分为两类,一类是类( Class )组件,一类是函数( Function )组件。

# 什么是 React 组件

代码语言:javascript复制
/** 类 */
class textClass {
  sayHello = () => {
    console.log("Hello");
  }
}

/** 类组件 */
class Index extends React.Component {
  state = {
    message: "Hello World"
  };
  sayHello = () => {
    this.setState({
      message: "Hello Cell"
    });
  };
  render () {
    return <div
      style={{marginTop: '30px'}}
      onClick={this.sayHello}
    >
      {this.state.message}
    </div>;
  }
}

/** 函数 */
function textFun() {
  return 'Hello World';
}

/** 函数组件 */
function FunComponent() {
  const [message, setMessage] = useState('Hello World');
  const sayHello = () => {
    setMessage('Hello Cell');
  };
  return <div
    style={{marginTop: '30px'}}
    onClick={sayHello}
  >
    {message}
  </div>;
}

组件本质上就是类和函数,但是与常规的类和函数不同的是,组件承载了渲染视图的 UI 和更新视图的 setState 、 useState 等方法。React 在底层逻辑上会像正常实例化类和正常执行函数那样处理的组件。

函数与类上的特性在 React 组件上同样具有,比如原型链,继承,静态属性等,所以不要把 React 组件和类与函数独立开来。

React 对组件的处理流程:

对于类组件的执行,是在 react-reconciler/src/ReactFiberClassComponent.js

代码语言:javascript复制
function constructClassInstance(
  workInProgress, // 当前正在工作的 fiber 对象
  ctor,           // 我们的类组件
  props           // props 
){
    /* 实例化组件,得到组件实例 instance */
    const instance = new ctor(props, context)
}

对于函数组件的执行,是在 react-reconciler/src/ReactFiberHooks.js

代码语言:javascript复制
function renderWithHooks(
  current,          // 当前函数组件对应的 `fiber`, 初始化
  workInProgress,   // 当前正在工作的 fiber 对象
  Component,        // 我们函数组件
  props,            // 函数组件第一个参数 props
  secondArg,        // 函数组件其他参数
  nextRenderExpirationTime, //下次渲染过期时间
){
    /* 执行我们的函数组件,得到 return 返回的 React.element对象 */
    let children = Component(props, secondArg);
}

# 类组件和函数组件

# Class 类组件

# 类组件的定义

class 组件中,除了继承 React.Component ,底层还加入了 updater 对象,组件中调用的 setStateforceUpdate 本质上是调用了 updater 对象上的 enqueueSetStateenqueueForceUpdate 方法。

React 底层定义类组件 react/src/ReactBaseClasses.js

代码语言:javascript复制
function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}
// setState
Component.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
// forceUpdate
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

类组件执行构造函数过程中会在实例上绑定 propscontext ,初始化置空 refs 属性,原型链上绑定setStateforceUpdate 方法。对于 updater,React 在实例化类组件之后会单独绑定 update 对象。

Q:如果没有在 constructorsuper 函数中传递 props,那么接下来 constructor 执行上下文中就获取不到 props ,为什么?

A: 绑定 props 是在父类 Component 构造函数中,执行 super 等于执行 Component 函数,此时 props 没有作为第一个参数传给 super() ,在 Component 中就会找不到 props 参数,从而变成 undefined ,在接下来 constructor 代码中打印 propsundefined

类组件各个部分的功能:

代码语言:javascript复制
class Index extends React.Component{
    constructor(...arg){
      super(...arg)                        /* 执行 react 底层 Component 函数 */
    }
    state = {}                              /* state */
    static number = 1                       /* 内置静态属性 */
    handleClick= () => console.log(111)     /* 方法: 箭头函数方法直接绑定在this实例上 */
    componentDidMount(){                    /* 生命周期 */
      console.log(Index.number,Index.number1) // 打印 1 , 2 
    }
    render(){                               /* 渲染函数 */
      return <div style={{ marginTop:'50px' }} onClick={ this.handerClick }  >hello,React!</div>
    }
}
Index.number1 = 2                           /* 外置静态属性 */
Index.prototype.handleClick = ()=> console.log(222) /* 方法: 绑定在 Index 原型链的 方法*/

# 函数组件

React V16.8 hooks 问世以来,对函数组件的功能加以强化,可以在 function 组件中,做类组件一切能做的事情,甚至完全取缔类组件。

代码语言:javascript复制
function Index() {
  console.log(Index.number); /** 打印 1 */
  const [message, setMessage] = useState('Hello World');
  return <div
    onClick={() => setMessage('Hello Cell')}
  >
    {message}
  </div>;
}
Index.number = 1; /** 静态属性 */

不要试图给函数组件 prototype 绑定属性或方法,即使绑定了也没有用,因为 React 对函数组件的调用,是采用直接执行函数的方式,而不是 new 的方式。

函数组件和类组件本质的区别是什么?对于类组件来说,底层只需要实例化一次,实例中保存了组件的 state 等状态。对于每一次更新只需要调用 render 方法以及对应的生命周期就可以了。但是在函数组件中,每一次更新都是一次新的函数执行,一次函数组件的更新,里面的变量会重新声明。

为了能让函数组件可以保存一些状态,执行一些副作用钩子,React Hooks 应运而生,它可以帮助记录 React 中组件的状态,处理一些额外的副作用。

# 组件通信方式

# props 和 callback

propscallback 可以作为 React 组件最基本的通信方式,父组件可以通过 props 将信息传递给子组件,子组件可以通过执行 props 中的回调函数 callback 来触发父组件的方法,实现父与子的消息通讯。

父组件 -> 通过自身 state 改变,重新渲染,传递 props -> 通知子组件

子组件 -> 通过调用父组件 props 方法 -> 通知父组件

代码语言:javascript复制
/** 子组件 */
function child(props) {
  const { parentSay, sayToParent } = props;
  return <div className='child'>
    child
    <div>parent say: {parentSay} </div>
    <input placeholder='say to parent' onChange={(e) => sayToParent(e.target.value)} />
  </div>;
}

/** 父组件 */
function parent() {
  const [ childSay, setChildSay ] = useState('');
  const [ parentSay, setParentSay ] = useState('');

  return <div>
    parent
    <div>child say: {childSay}</div>
    <input placeholder='say to child' onChange={(e) => setParentSay(e.target.value)} />
    <Child parentSay={parentSay} sayToParent={setChildSay} />
  </div>;
}

# ref

# React-redux 或 React-mobx

# context 上下文

# Event Bus 事件总线

可以利用 eventBus 也可以实现组件通信,但是在 React 中并不提倡用这种方式。

代码语言:javascript复制
import { BusService } from './eventBus';
/** 子组件 */
function Child() {
  const [ parentSay, setParentSay ] = useState('');

  React.useEffect(() => {
    BusService.on('parentSay', (v) => {
      setParentSay(v);
    });

    return () => {
      BusService.off('parentSay');
    };
  }, []);

  return <div className='child'>
    child
    <div>parent say: {parentSay} </div>
    <input placeholder='say to parent' onChange={(e) => BusService.emit('childSay', e.target.value)} />
  </div>;
}

/** 父组件 */
function Parent() {
  const [ childSay, setChildSay ] = useState('');

  React.useEffect(() => {
    BusService.on('childSay', (v) => {
      setChildSay(v);
    });

    return () => {
      BusService.off('childSay');
    };
  }, []);

  return <div>
    parent
    <div>child say: {childSay}</div>
    <input placeholder='say to child' onChange={(e) => BusService.emit('parentSay', e.target.value)} />
    <Child />
  </div>;
}

eventBus 不仅达到了和使用 props 同样的效果,还能跨层级,不会受到 React 父子组件层级的影响。

但是很多地方还是不推荐使用,因为其有一些缺点:

  • 需要手动绑定和解绑事件,容易出错
  • 对于小型项目还好,对于中大型项目,这种方式的组件通信会造成牵一发动全身的影响,后期难以维护,并且组件之间的状态也是未知的
  • 在一定程度上违背了 React 的单向数据流的设计思想

# 组件强化方式

# 类组件继承

因为 React 中类组件,有良好的继承属性,所以可以针对一些基础组件,首先实现一部分基础功能,再针对项目要求进行有方向的改造、强化、添加额外功能

代码语言:javascript复制
class Person extends React.Component {
  constructor(props) {
    super(props);
    console.log('Person constructor');
  }
  componentDidMount() {
    console.log('Person componentDidMount');
  }
  eat() {}
  sleep() {}
  render() {
    return <div>Person</div>;
  }
}

class Developer extends Person {
  constructor(props) {
    super(props);
    console.log('Developer constructor');
  }
  componentDidMount() {
    console.log('Developer componentDidMount');
    console.log(this);
  }
  coding() {}
  render() {
    return <div>
      {super.render()}
      Also Developer
    </div>;
  }
}

export default Developer;

继承增强的优势:

  • 可以控制父类 render(),还可以添加一些其他的渲染内容
  • 可以共享父类方法,还可以添加额外的方法和属性

需要注意的地方:

  • state 和 生命周期会被继承后的组件修改,如 Person 中的 componentDidMount 会被 Developer 中的 componentDidMount 覆盖

# 函数组件自定义 Hooks

# HOC 高阶组件

0 人点赞