React 源码漂流(二)之 Component

 
  

一、组件

1. 纯组件

React.PureComponent ,和 React.Component 类似,都是定义一个组件类。不同是 React.Component 没有实现 shouldComponentUpdate(),而 React.PureComponent 通过 propsstate浅比较实现了。

// React.PureComponent 纯组件class Counter extends React.PureComponent {  constructor(props) {    super(props);    this.state = {count: 0};  }  render() {    return (      <button onClick={() => this.setState(state => ({count: state.count + 1}))}>        Count: {this.state.count}      </button>    );  }}
class Counter extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {count0};
  }
  render() {
    return (
      <button onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>

    );
  }
}

在下一节中将会详细介绍。

2. 函数组件

定义React组件的最简单方式就是定义一个函数组件,它接受单一的 props 并返回一个React元素。

// 函数组件function Counter(props) {    return <div>Counter: {props.count}</div>}// 类组件class Counter extends React.Component {  render() {    return <div>Counter: {this.props.count}</div>  }}
function Counter(props{
    return <div>Counter: {props.count}</div>
}
// 类组件
class Counter extends React.Component {
  render() {
    return <div>Counter: {this.props.count}</div>
  }
}

3. 受控组件与非受控组件

受控和非受控主要是取决于组件是否受父级传入的 props 控制

用 props 传入数据的话,组件可以被认为是受控(因为组件被父级传入的 props 控制)。

数据只保存在组件内部的 state 的话,是非受控组件(因为外部没办法直接控制 state)。

export default class AnForm extends React.Component {  state = {    name: ""  }  handleSubmitClick = () => {    console.log("非受控组件: ", this._name.value);    console.log("受控组件: ", this.state.name);  }  handleChange = (e) => {    this.setState({      name: e.target.value    })  }  render() {    return (      <form onSubmit={this.handleSubmitClick}>      <label>        非受控组件:        <input             type="text"             defaultValue="default"             ref={input => this._name = input}         />      </label>      <label>        受控组件:        <input             type="text"             value={this.state.name}             onChange={this.handleChange}        />      </label>      <input type="submit" value="Submit" />    </form>    );  }}default class AnForm extends React.Component {
  state = {
    name""
  }
  handleSubmitClick = () => {
    console.log("非受控组件: "this._name.value);
    console.log("受控组件: "this.state.name);
  }
  handleChange = (e) => {
    this.setState({
      name: e.target.value
    })
  }

  render() {
    return (
      <form onSubmit={this.handleSubmitClick}>
      <label>
        非受控组件:
        <input 
            type="text" 
            defaultValue="default" 
            ref={input => this._name = input} 
        />
      </label>
      <label>
        受控组件:
        <input 
            type="text" 
            value={this.state.name} 
            onChange={this.handleChange}
        />
      </label>
      <input type="submit" value="Submit" />
    </form>
    );
  }
}
受控组件

与 html 不同的是,在 React 中,<input><select><textarea>等这类组件,不会主动维持自身状态,并根据用户输入进行更新。它们都要绑定一个onChange事件;每当状态发生变化时,都要写入组件的 state 中,在 React 中被称为受控组件

export default class AnForm extends React.Component {  constructor(props) {    super(props);    this.state = {value: ""};    this.handleChange = this.handleChange.bind(this);  }  handleChange(event) {    this.setState({value: event.target.value});  }  render() {    return <input                   type="text"                   value={this.state.value}                   onChange={this.handleChange}               />;  }}default class AnForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value""};
    this.handleChange = this.handleChange.bind(this);
  }
  handleChange(event) {
    this.setState({value: event.target.value});
  }
  render() {
    return <input 
                  type="text" 
                  value={this.state.value} 
                  onChange={this.handleChange} 
              />
;
  }
}
非受控组件

非受控组件不再将数据保存在 state,而使用 refs,将真实数据保存在 DOM 中。

export default class AnForm extends Component {  handleSubmitClick = () => {    const name = this._name.value;  }  render() {    return (      <div>        <input type="text" ref={input => this._name = input} />        <button onClick={this.handleSubmitClick}>Sign up</button>      </div>    );  }}default class AnForm extends Component {
  handleSubmitClick = () => {
    const name = this._name.value;
  }

  render() {
    return (
      <div>
        <input type="text" ref={input => this._name = input} />
        <button onClick={this.handleSubmitClick}>Sign up</button>
      </div>
    );
  }
}

4. 有状态组件与无状态组件

有状态组件

通过 state 管理状态

export default class Counter extends React.Component {  constructor(props) {    super(props)    this.state = { clicks: 0 }    this.handleClick = this.handleClick.bind(this)  }  handleClick() {    this.setState(state => ({ clicks: state.clicks + 1 }))  }  render() {    return (      <Button        onClick={this.handleClick}        text={`You've clicked me ${this.state.clicks} times!`}      />    )  }}default class Counter extends React.Component {
  constructor(props) {
    super(props)
    this.state = { clicks0 }
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick() {
    this.setState(state => ({ clicks: state.clicks + 1 }))
  }
  render() {
    return (
      <Button
        onClick={this.handleClick}
        text={`You've clicked me ${this.state.clickstimes!`}
      />

    )
  }
}
无状态组件

输入输出数据完全由props决定,而且不会产生任何副作用。

const Button = props =>  <button onClick={props.onClick}>    {props.text}  </button>props =>
  <button onClick={props.onClick}>
    {props.text}
  </button>

5. 展示组件与容器组件

展示组件

展示组件指不关心数据是怎么加载和变动的,只关注于页面展示效果的组件。

class TodoList extends React.Component{    constructor(props){        super(props);    }    render(){        const {todos} = this.props;        return (<div>                <ul>                    {todos.map((item,index)=>{                        return <li key={item.id}>{item.name}</li>                    })}                </ul>            </div>)    }}
    constructor(props){
        super(props);
    }
    render(){
        const {todos} = this.props;
        return (<div>
                <ul>
                    {todos.map((item,index)=>{
                        return <li key={item.id}>{item.name}</li>
                    })}
                </ul>
            </div>
)
    }
}
容器组件

容器组件只关心数据是怎么加载和变动的,而不关注于页面展示效果。

//容器组件class TodoListContainer extends React.Component{    constructor(props){        super(props);        this.state = {            todos:[]        }        this.fetchData = this.fetchData.bind(this);    }    componentDidMount(){        this.fetchData();    }    fetchData(){        fetch('/api/todos').then(data =>{            this.setState({                todos:data            })        })    }    render(){        return (<div>                <TodoList todos={this.state.todos} />                </div>)    }}
class TodoListContainer extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            todos:[]
        }
        this.fetchData = this.fetchData.bind(this);
    }
    componentDidMount(){
        this.fetchData();
    }
    fetchData(){
        fetch('/api/todos').then(data =>{
            this.setState({
                todos:data
            })
        })
    }
    render(){
        return (<div>
                <TodoList todos={this.state.todos} />    
            </div>)
    }
}

6. 高阶组件

高阶函数的定义:接收函数作为输入,或者输出另一个函数的一类函数,被称作高阶函数。

对于高阶组件,它描述的便是接受 React 组件作为输入,输出一个新的 React 组件的组件。

更通俗的描述为,高阶组件通过包裹(wrapped)被传入的 React 组件,经过一系列处理,最终返回一个相对增强(enhanced)的 React 组件,供其他组件调用。使我们的代码更具有复用性、逻辑性和抽象特性,它可以对 render 方法做劫持,也可以控制  props 、state

实现高阶组件的方法有以下两种:

// 属性代理export default function withHeader(WrappedComponent) {  return class HOC extends React.Component { // 继承与 React.component    render() {      const newProps = {        test:'hoc'      }      // 透传props,并且传递新的newProps      return <div>        <WrappedComponent {...this.props} {...newProps}/>       </div>    }  }}// 反向继承export default function (WrappedComponent) {  return class Inheritance extends WrappedComponent { // 继承于被包裹的 React 组件    componentDidMount() {      // 可以方便地得到state,做一些更深入的修改。      console.log(this.state);    }    render() {      return super.render();    }  }}
export default function withHeader(WrappedComponent{
  return class HOC extends React.Component // 继承与 React.component
    render() {
      const newProps = {
        test:'hoc'
      }
      // 透传props,并且传递新的newProps
      return <div>
        <WrappedComponent {...this.props} {...newProps}/> 
      </div>
    }
  }
}

// 反向继承
export default function (WrappedComponent) {
  return class Inheritance extends WrappedComponent { // 继承于被包裹的 React 组件
    componentDidMount() {
      // 可以方便地得到state,做一些更深入的修改。
      console.log(this.state);
    }
    render() {
      return super.render();
    }
  }
}

7. Hook 组件

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

但与 class 生命周期不同的是,Hook 更接近于实现状态同步,而不是响应生命周期事件。

import React, { useState, useEffect } from 'react';function Example() {  // 声明一个叫 "count" 的 state 变量  const [count, setCount] = useState(0);  useEffect(()=>{    // 需要在 componentDidMount 执行的内容    return function cleanup() {      // 需要在 componentWillUnmount 执行的内容            }  }, [])  useEffect(() => {     // 在 componentDidMount,以及 count 更改时 componentDidUpdate 执行的内容    document.title = 'You clicked ' + count + ' times';     return () => {      // 需要在 count 更改时 componentDidUpdate(先于 document.title = ... 执行,遵守先清理后更新)      // 以及 componentWillUnmount 执行的内容           } // 当函数中 Cleanup 函数会按照在代码中定义的顺序先后执行,与函数本身的特性无关  }, [count]); // 仅在 count 更改时更新  return (    <div>      <p>You clicked {count} times</p>      <button onClick={() => setCount(count + 1)}>        Click me      </button>    </div>  );}from 'react';

function Example({
  // 声明一个叫 "count" 的 state 变量
  const [count, setCount] = useState(0);

  useEffect(()=>{
    // 需要在 componentDidMount 执行的内容
    return function cleanup({
      // 需要在 componentWillUnmount 执行的内容      
      }
  }, [])

  useEffect(() => { 
    // 在 componentDidMount,以及 count 更改时 componentDidUpdate 执行的内容
    document.title = 'You clicked ' + count + ' times'
    return () => {
      // 需要在 count 更改时 componentDidUpdate(先于 document.title = ... 执行,遵守先清理后更新)
      // 以及 componentWillUnmount 执行的内容       
    } // 当函数中 Cleanup 函数会按照在代码中定义的顺序先后执行,与函数本身的特性无关
  }, [count]); // 仅在 count 更改时更新

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>

  );
}

二、Component 源码解读

首先看一下 React.Component 结构

// ReactBaseClasses.js 文件/** * Base class helpers for the updating state of a component. */function Component(props, context, updater) {  this.props = props; // 属性 props  this.context = context; // 上下文 context  // If a component has string refs, we will assign a different object later.  // 初始化 refs,为 {},主要在 stringRef 中使用,将 stringRef 节点的实例挂载在 this.refs 上  this.refs = emptyObject;   // We initialize the default updater but the real one gets injected by the  // renderer.  this.updater = updater || ReactNoopUpdateQueue; // updater}Component.prototype.isReactComponent = {};/** * 设置 state 的子集,使用该方法更新 state,避免 state 的值为可突变的状态 * `shouldComponentUpdate`只是浅比较更新, * 可突变的类型可能导致 `shouldComponentUpdate` 返回 false,无法重新渲染 * Immutable.js 可以解决这个问题。它通过结构共享提供不可突变的,持久的集合: * 不可突变: 一旦创建,集合就不能在另一个时间点改变。 * 持久性: 可以使用原始集合和一个突变来创建新的集合。原始集合在新集合创建后仍然可用。 * 结构共享: 新集合尽可能多的使用原始集合的结构来创建,以便将复制操作降至最少从而提升性能。 * * 并不能保证 `this.state` 通过 `setState` 后不可突变的更新,它可能还返回原来的数值 * 不能保证 `setrState` 会同步更新 `this.state` * `setState` 是通过队列形式来更新 state ,当 执行 `setState` 时, * 会把 state 浅合并后放入状态队列,然后批量执行,即它不是立即更新的。 * 不过,你可以在 callback 回调函数中获取最新的值 *  * 注意:对于异步渲染,我们应在 `getSnapshotBeforeUpdate` 中读取 `state`、`props`, * 而不是 `componentWillUpdate` * * @param {object|function} partialState Next partial state or function to *        produce next partial state to be merged with current state. * @param {?function} callback Called after state is updated. * @final * @protected */Component.prototype.setState = function(partialState, callback) {  // 当 partialState 状态为 object 或 function类型 或 null 时,  // 执行 this.updater.enqueueSetState 方法,否则报错  invariant(    typeof partialState === 'object' ||      typeof partialState === 'function' ||      partialState == null,    'setState(...): takes an object of state variables to update or a ' +      'function which returns an object of state variables.',  );  // 将 `setState` 事务放入队列中  this.updater.enqueueSetState(this, partialState, callback, 'setState'); };/** * 强制更新,当且仅当当前不处于 DOM 事物(transaction)中才会被唤起 * This should only be invoked when it is known with * certainty that we are **not** in a DOM transaction. *  * 默认情况下,当组件的state或props改变时,组件将重新渲染。 * 如果你的`render()`方法依赖于一些其他的数据, * 你可以告诉React组件需要通过调用`forceUpdate()`重新渲染。  * 调用`forceUpdate()`会导致组件跳过 `shouldComponentUpdate()`, * 直接调用 `render()`。但会调用 `componentWillUpdate` 和 `componentDidUpdate`。 * 这将触发组件的正常生命周期方法,包括每个子组件的 shouldComponentUpdate() 方法。  * forceUpdate 就是重新 render 。 * 有些变量不在 state 上,当时你又想达到这个变量更新的时候,刷新 render ; * 或者 state 里的某个变量层次太深,更新的时候没有自动触发 render 。 * 这些时候都可以手动调用 forceUpdate 自动触发 render *  * @param {?function} callback 更新完成后的回调函数. * @final * @protected */Component.prototype.forceUpdate = function(callback) {  // updater 强制更新  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); };
/**
 * Base class helpers for the updating state of a component.
 */

function Component(props, context, updater{
  this.props = props; // 属性 props
  this.context = context; // 上下文 context
  // If a component has string refs, we will assign a different object later.
  // 初始化 refs,为 {},主要在 stringRef 中使用,将 stringRef 节点的实例挂载在 this.refs 上
  this.refs = emptyObject; 
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue; // updater
}

Component.prototype.isReactComponent = {};

/**
 * 设置 state 的子集,使用该方法更新 state,避免 state 的值为可突变的状态
 * `shouldComponentUpdate`只是浅比较更新,
 * 可突变的类型可能导致 `shouldComponentUpdate` 返回 false,无法重新渲染
 * Immutable.js 可以解决这个问题。它通过结构共享提供不可突变的,持久的集合:
 * 不可突变: 一旦创建,集合就不能在另一个时间点改变。
 * 持久性: 可以使用原始集合和一个突变来创建新的集合。原始集合在新集合创建后仍然可用。
 * 结构共享: 新集合尽可能多的使用原始集合的结构来创建,以便将复制操作降至最少从而提升性能。
 *
 * 并不能保证 `this.state` 通过 `setState` 后不可突变的更新,它可能还返回原来的数值
 * 不能保证 `setrState` 会同步更新 `this.state`
 * `setState` 是通过队列形式来更新 state ,当 执行 `setState` 时,
 * 会把 state 浅合并后放入状态队列,然后批量执行,即它不是立即更新的。
 * 不过,你可以在 callback 回调函数中获取最新的值
 * 
 * 注意:对于异步渲染,我们应在 `getSnapshotBeforeUpdate` 中读取 `state`、`props`,
 * 而不是 `componentWillUpdate`
 *
 * @param {object|function} partialState Next partial state or function to
 *        produce next partial state to be merged with current state.
 * @param {?function} callback Called after state is updated.
 * @final
 * @protected
 */

Component.prototype.setState = function(partialState, callback{
  // 当 partialState 状态为 object 或 function类型 或 null 时,
  // 执行 this.updater.enqueueSetState 方法,否则报错
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  // 将 `setState` 事务放入队列中
  this.updater.enqueueSetState(this, partialState, callback, 'setState'); 
};

/**
 * 强制更新,当且仅当当前不处于 DOM 事物(transaction)中才会被唤起
 * This should only be invoked when it is known with
 * certainty that we are **not** in a DOM transaction.
 * 
 * 默认情况下,当组件的state或props改变时,组件将重新渲染。
 * 如果你的`render()`方法依赖于一些其他的数据,
 * 你可以告诉React组件需要通过调用`forceUpdate()`重新渲染。 
 * 调用`forceUpdate()`会导致组件跳过 `shouldComponentUpdate()`,
 * 直接调用 `render()`。但会调用 `componentWillUpdate` 和 `componentDidUpdate`。
 * 这将触发组件的正常生命周期方法,包括每个子组件的 shouldComponentUpdate() 方法。 
 * forceUpdate 就是重新 render 。
 * 有些变量不在 state 上,当时你又想达到这个变量更新的时候,刷新 render ;
 * 或者 state 里的某个变量层次太深,更新的时候没有自动触发 render 。
 * 这些时候都可以手动调用 forceUpdate 自动触发 render
 * 
 * @param {?function} callback 更新完成后的回调函数.
 * @final
 * @protected
 */

Component.prototype.forceUpdate = function(callback{
  // updater 强制更新
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); 
};

其中 this.refs  值 emptyObject 为:

// 设置 refs 初始值为 {}const emptyObject = {};if (__DEV__) {  Object.freeze(emptyObject); // __DEV__ 模式下, 冻结 emptyObject}// Object.freeze() 冻结一个对象,被冻结的对象不能被修改(添加,删除,// 修改已有属性的可枚举性、可配置性、可写性与属性值,原型);返回和传入的参数相同的对象。
const emptyObject = {};
if (__DEV__) {
  Object.freeze(emptyObject); // __DEV__ 模式下, 冻结 emptyObject
}
// Object.freeze() 冻结一个对象,被冻结的对象不能被修改(添加,删除,
// 修改已有属性的可枚举性、可配置性、可写性与属性值,原型);返回和传入的参数相同的对象。

ReactNoopUpdateQueue 为:

// ReactNoopUpdateQueue.js 文件/** * 这是一个关于 更新队列(update queue) 的抽象 API */const ReactNoopUpdateQueue = {  /**   * 检查复合组件是否装载完成(被插入树中)   * @param {ReactClass} publicInstance 测试实例单元   * @return {boolean} 装载完成为 true,否则为 false   * @protected   * @final   */  isMounted: function(publicInstance) {    return false;  },  /**   * 强制更新队列,当且仅当当前不处于 DOM 事物(transaction)中才会被唤起   *   * 当 state 里的某个变量层次太深,更新的时候没有自动触发 render 。   * 这些时候就可以调用该方法强制更新队列   *   * 该方法将跳过 `shouldComponentUpdate()`, 直接调用 `render()`, 但它会唤起   * `componentWillUpdate` 和 `componentDidUpdate`.   *   * @param {ReactClass} publicInstance 将被重新渲染的实例   * @param {?function} callback 组件更新后的回调函数.   * @param {?string} callerName 在公共 API 调用该方法的函数名称.   * @internal   */  enqueueForceUpdate: function(publicInstance, callback, callerName) {    warnNoop(publicInstance, 'forceUpdate');  },  /**   * 完全替换state,与 `setState` 不同的是,`setState` 是以修改和新增的方式改变 `state `的,   * 不会改变没有涉及到的 `state`。   * 而 `enqueueReplaceState` 则用新的 `state` 完全替换掉老 `state`   * 使用它或 `setState` 来改变 state,并且应该把 this.state 设置为不可突变类型对象,   * 并且this.state不会立即更改   * 我们应该在回调函数 callback 中获取最新的 state   *   * @param {ReactClass} publicInstance The instance that should rerender.   * @param {object} completeState Next state.   * @param {?function} callback Called after component is updated.   * @param {?string} callerName name of the calling function in the public API.   * @internal   */  enqueueReplaceState: function(    publicInstance,    completeState,    callback,    callerName,  ) {    warnNoop(publicInstance, 'replaceState');  },  /**   * 设置 state 的子集   * 它存在的唯一理由是 _pendingState 是内部方法。   * `enqueueSetState` 实现浅合并更新 `state`   *   * @param {ReactClass} publicInstance The instance that should rerender.   * @param {object} partialState Next partial state to be merged with state.   * @param {?function} callback Called after component is updated.   * @param {?string} Name of the calling function in the public API.   * @internal   */  enqueueSetState: function(    publicInstance,    partialState,    callback,    callerName,  ) {    warnNoop(publicInstance, 'setState');  },};export default ReactNoopUpdateQueue;
/**
 * 这是一个关于 更新队列(update queue) 的抽象 API
 */

const ReactNoopUpdateQueue = {
  /**
   * 检查复合组件是否装载完成(被插入树中)
   * @param {ReactClass} publicInstance 测试实例单元
   * @return {boolean} 装载完成为 true,否则为 false
   * @protected
   * @final
   */

  isMounted: function(publicInstance{
    return false;
  },

  /**
   * 强制更新队列,当且仅当当前不处于 DOM 事物(transaction)中才会被唤起
   *
   * 当 state 里的某个变量层次太深,更新的时候没有自动触发 render 。
   * 这些时候就可以调用该方法强制更新队列
   *
   * 该方法将跳过 `shouldComponentUpdate()`, 直接调用 `render()`, 但它会唤起
   * `componentWillUpdate` 和 `componentDidUpdate`.
   *
   * @param {ReactClass} publicInstance 将被重新渲染的实例
   * @param {?function} callback 组件更新后的回调函数.
   * @param {?string} callerName 在公共 API 调用该方法的函数名称.
   * @internal
   */

  enqueueForceUpdate: function(publicInstance, callback, callerName{
    warnNoop(publicInstance, 'forceUpdate');
  },

  /**
   * 完全替换state,与 `setState` 不同的是,`setState` 是以修改和新增的方式改变 `state `的,
   * 不会改变没有涉及到的 `state`。
   * 而 `enqueueReplaceState` 则用新的 `state` 完全替换掉老 `state`
   * 使用它或 `setState` 来改变 state,并且应该把 this.state 设置为不可突变类型对象,
   * 并且this.state不会立即更改
   * 我们应该在回调函数 callback 中获取最新的 state
   *
   * @param {ReactClass} publicInstance The instance that should rerender.
   * @param {object} completeState Next state.
   * @param {?function} callback Called after component is updated.
   * @param {?string} callerName name of the calling function in the public API.
   * @internal
   */

  enqueueReplaceState: function(
    publicInstance,
    completeState,
    callback,
    callerName,
  
{
    warnNoop(publicInstance, 'replaceState');
  },

  /**
   * 设置 state 的子集
   * 它存在的唯一理由是 _pendingState 是内部方法。
   * `enqueueSetState` 实现浅合并更新 `state`
   *
   * @param {ReactClass} publicInstance The instance that should rerender.
   * @param {object} partialState Next partial state to be merged with state.
   * @param {?function} callback Called after component is updated.
   * @param {?string} Name of the calling function in the public API.
   * @internal
   */

  enqueueSetState: function(
    publicInstance,
    partialState,
    callback,
    callerName,
  
{
    warnNoop(publicInstance, 'setState');
  },
};

export default ReactNoopUpdateQueue;

注意,React API 只是简单的功能介绍,具体的实现是在 react-dom 中,这是因为不同的平台,React API 是一致的,但不同的平台,渲染的流程是不同的,具体的 Component 渲染流程不一致,会根据具体的平台去定制。

组件生命周期请参考  Hooks 与 React 生命周期的关系 

❤️ 看完三件事

如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:

  1. 点个「在看」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)

  2. 关注我的官网 https://muyiy.vip,让我们成为长期关系

  3. 关注公众号「高级前端进阶」,每周重点攻克一个前端面试重难点,公众号后台回复「面试题」 送你高级前端面试题。

640?wx_fmt=png

猜你喜欢

转载自blog.csdn.net/github_34708151/article/details/98562205