X

曜彤.手记

随手写写,关于互联网技术、产品与创业

React 知识点整理


只是稍微整理了下 React 的一些知识点,nothing special。

Context API

Context API 主要用来解决通过 props 传递数据时需要经过多层中间组件才能到达子组件的情况。首先考虑场景是否可以通过组件的“组合方式”来解决,通过 “IOC” 模式将子组件的组装渲染过程提升到父组件中完成,这样中间组件便不再需要关心具体的数据流动情况,只需要传递组件本身到子组件特定的 slot 处即可。

Context API 的几个不足之处:

  1. 不受 componentDidUpdate 的控制,Provider 数据更新时会强制重新绘制,这样所有订阅了同一 Provider 的子组件都会被强制更新。该生命周期函数只会影响 componentDidUpdate 的执行,但不影响组件被重新渲染;
  2. 类组件下的 static 模式只能订阅距离最近的一个 Context Provider,如需订阅多个 Provider,则需要使用 <CompanyContext.Consumer />
  3. 考虑 Context 只是作为 Redux 之类的状态管理容器的一种轻量级补充;
// React v16.8.6;

// create context object;
const CompanyContext = React.createContext();

class CompanyContainer extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      companyName: 'Mega Corp',
      employees: 1234567890,
      name: 'J.C. Hiatt',
      teamName: 'Knights of the Night',
      teams: 50000,
      title: 'Founder @ DevLifts'
    }
  }

  change = () => {
     this.setState({
       companyName: 'Alibaba, inc'
     })
  }

  render() {
    const { companyName, employees, name, teamName, teams, title } = this.state;

    return (
      // create a Provider from the Context, pass in state values;
      <CompanyContext.Provider
        value={{
          companyName,
          employees,
          name,
          teamName,
          teams,
          title,
        }} >
        <button onClick={this.change}>Change</button>
        <Company/>
      </CompanyContext.Provider>
    )
  }
}

class Company extends React.Component {
  componentDidUpdate() {
    console.log('[Company] component updated!')
  }

  shouldComponentUpdate() {
    return false;
  }

  render() {
    return (
      <CompanyContext.Consumer>
        {({ companyName, employees, teams }) => (
          <React.Fragment>
            <h1>Company name: <span>{companyName}</span></h1>
            <p>Teams: <span>{teams}</span></p>
            <Team />
            <p>Employees: <span>{employees}</span></p>
            <Employee />
          </React.Fragment>
        )}
      </CompanyContext.Consumer>
    )
  }
}

class Team extends React.Component {
  // append to the context with "contextType";
  static contextType = CompanyContext;

  componentDidUpdate() {
    console.log('[Team] component updated!')
  }

  render() {
    return (
      <p>I'm on the <span>{this.context.teamName}</span> team.</p>
    )
  }
}


class Employee extends React.Component {
  componentDidUpdate() {
    console.log('[Employee] component updated!')
  }

  render() {
    return (
      <CompanyContext.Consumer>
        {({ name, title }) => (
          <p>I'm an employee. My name is <span>{name}</span>, and my title is <span>{title}</span>.</p>
        )}
      </CompanyContext.Consumer>
    )
  }
}

ReactDOM.render(<CompanyContainer />, document.getElementById("root"));

Error Boundaries

只用于捕捉子组件在生命周期中发生的错误,然后给予特定的 fallback,但无法捕捉下列情况的异常:

  1. 事件处理器;
  2. 异步代码;
  3. SSR;
  4. Error Boundaries 类本身;
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // update state so the next render will show the fallback UI;
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // you can also log the error to an error reporting service;
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      // you can render any custom fallback UI;
      return <h1>Something went wrong.</h1>;
    }
    // render the children;
    return this.props.children; 
  }
}
<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

Forwarding Refs

让父组件可以引用到子组件内部的 ref。

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

class FancyInputInner extends React.PureComponent {
  render() {
    return (
      <input type="text" ref={this.props.innerRef} className="FancyInput"></input>
    )
  }
}

// like HOC;
const FancyInput = React.forwardRef((props, ref) => <FancyInputInner {...props} innerRef={ref} />);

class App extends React.PureComponent {
  constructor(props) {
    super(props);
    this.buttonRef = React.createRef();
    this.inputRef = React.createRef();
  }

  componentDidMount() {
    // print the references;
    console.log(this.buttonRef)
    console.log(this.inputRef)
  }

  render() {
    return (
      <>
        <FancyButton ref={this.buttonRef}>Click Here</FancyButton>
        <FancyInput ref={this.inputRef}></FancyInput>
      </>
    );
  }
}


ReactDOM.render(<App />, document.getElementById("root"));

HOC (Higher-Order Components)

一个 HOC 其实就是一个方法(函数)。该方法接受一个组件对象,经过处理后返回一个新的组件对象。注意:HOC 不要修改传入组件本身的结构(如各类生命周期函数)。

几个惯例:

  1. 将与 HOC 本身处理流程无关的 props 应直接传递给子组件;
  2. 常见的 HOC 签名类型:Component => Component,可以用类似 “compose” 的方法来组合执行多个 HOC;
  3. 为导出的包装组件增加 “displayName / name” 属性,以利于浏览器调试;

几个注意事项:

  1. 不要在 render 方法里使用 HOC。每次会生成一个新的组件实例(类似 “data={...}” 或者 “handler={() => {...}”),导致渲染性能下降,并且丢失状态。对应的,可以在生命周期函数或者组件构造函数中使用;
  2. 使用 “hoist-non-react-statics” 来“复制”包装组件上的静态方法到新组件上;
  3. ref 的传递需要基于 React.forwardRef

JSX In Depth

JSX 只是一种 React.createElement(component, props, ...children) 函数的语法糖:

<MyButton color="blue" shadowSize={2}>Click Me</MyButton>
React.createElement(
  MyButton,
  {color: 'blue', shadowSize: 2},
  'Click Me'
);

运行时组件类型确定:

import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
  photo: PhotoStory,
  video: VideoStory
};

function Story(props) {
  // correct! JSX type can be a capitalized variable;
  const SpecificStory = components[props.storyType];
  return <SpecificStory story={props.story} />;
}

函数作为 props.children,被渲染成合法的组件结构:

function Repeat(props) {
  let items = [];
  for (let i = 0; i < props.numTimes; i++) {
    items.push(props.children(i));
  }
  return <div>{items}</div>;
}

function ListOfTenThings() {
  return (
    <Repeat numTimes={10}>
      {(index) => <div key={index}>This is item {index} in the list</div>}
    </Repeat>
  );
}

Performance

shouldComponentUpdate 函数决定了是否进行 Node Diffing 的过程,但是否真正需要 Update 还是取决于前者的计算和比较结果。

而对于 PureComponent 浅比较无法处理的场景(比如同一个数组/对象引用),我们一般可以用如下的方式来处理,以产生新的状态对象结构:

function updateColorMap(colormap) {
  // Array.concat / Object.assign;
  return Object.assign({}, colormap, {right: 'blue'});
  // return {...colormap, right: 'blue'};
}

Portals

使用 ReactDOM.createPortal(child, container) 将一个组件渲染到不同的 DOM 节点上,一般适用于 Modal 组件这种有自己独立挂载点的场景。

class App extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return ReactDOM.createPortal(
      this.props.children,
      document.getElementById('modal-root')
    );
  }
}

ReactDOM.render(<App />, document.getElementById('app'));

React 事件的冒泡和捕获不基于真实的 DOM 结构,而基于 JSX 的结构。

class Modal extends React.Component {
  render() {
    return ReactDOM.createPortal(
      this.props.children,
      document.getElementById('modal-root'),
    );
  }
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {clicks: 0};
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    // event bubbling according to the JSX structure;
    this.setState(prevState => ({
      clicks: prevState.clicks + 1
    }));
  }

  render() {
    return (
      <div onClick={this.handleClick}>
        <p>Number of clicks: {this.state.clicks}</p>
        <Modal>
          <Child />
        </Modal>
      </div>
    );
  }
}

function Child() {
  return (
    <div className="modal">
      <button>Click</button>
    </div>
  );
}

ReactDOM.render(<Parent />, document.getElementById('app'));

Refs and the DOM

Refs 用来引用 DOM 或者类组件实例。不能用在函数组件上,因为其没有对应的实例。

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.ref = React.createRef();
  }
  render() {
    return <div ref={this.ref} />;
  }
}

Callback Refs:

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  // "this.inputElement" hold the DOM instance;
  render() {
    return (
      <CustomTextInput
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

Render Props

通过 props 来传递需要在父组件中渲染的子组件。

class Mouse extends React.PureComponent {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
        {// render the props component;}
        {this.props.render(this.state)}
      </div>
    );
  }
}

function withMouse(Component) {
  return class extends React.Component {
    renderTheComponent(mouse) {
      return <Component {...this.props} mouse={mouse} />;
    }

    render() {
      return (
        // a function will return corresponding rendered component;
        <Mouse render={this.renderTheComponent}/>
      );
    }
  }
}

Strict Mode

可以用来检查一些问题:

  1. 检测不安全的生命周期函数;
  2. 检测 legacy API 的使用;
  3. 检测已废弃的 findDOMNode 函数的使用;
  4. 检测非预料的副作用(譬如非幂等的初始化);
import React from 'react';

function App() {
  return (
    <div>
      <Header />
      <React.StrictMode>
        <div>
          <MyComponent />
        </div>
      </React.StrictMode>
      <Footer />
    </div>
  );
}

PropTypes

规定 props 的字段、对应类型以及默认值(类型检测)。

import PropTypes from 'prop-types';

class Greeting extends React.Component {
  static defaultProps = {
    name: 'YHSPY'
  }

  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

Greeting.propTypes = {
  name: PropTypes.string
};

Uncontrolled Components

表单控件的值由 DOM 元素自己来保存,而非组件的状态。在需要时通过 ref 取出。与可控组件不同的是,不可控组件无法做到及时与 UI 同步,因此不适用于表单即时反馈较强(如即时的输入检查)的场景。

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.input = React.createRef();
  }

  handleSubmit = (event) => {
    alert('A name was submitted: ' + this.input.current.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input
            defaultValue="Bob"
            type="text"
            ref={this.input} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

Hooks

解决了类组件或者之前 React 组件在大型项目中遇到的一系列问题:

  1. 难以在组件之间重用具有状态性的逻辑;
  2. 组件的数据处理逻辑被分散在各个生命周期函数中,流程难以被理解;
  3. 多个类组件之间的逻辑关系混乱复杂;
import React, { useState } from 'react';

function Example(props) {
  // declare a new state variable, which we'll call "count";
  const [count, setCount] = useState(0);
  // const [fruit, setFruit] = useState('banana');

  // we can use "useEffect" for multiple times;
  useEffect(() => {
    // component mount / update;
    document.title = `You clicked ${count} times`;
    return function () {
      // used for cleanup (every time re-rendering or unmount);
    };
  }, [count]);

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

Hooks 如何解决问题:

  1. 将组件逻辑抽象在 Hooks 中,以便于重用;
  2. 通过注入多个 useEffect 将相关的操作操作放在同一个地方进行;


这是文章底线,下面是评论
  暂无评论,欢迎勾搭 :)