快来跟我一起学 React(Day8)

简介

我们继续上一节的内容,开始分析 React 官网:https://reactjs.org/docs/accessibility.html 的 “高级指引” 部分,这一部分会涉及到性能优化、Portals、Render Props 等概念的分析,跟上节奏,我们一起出发吧!

知识点

  • 性能优化
  • Portals
  • Render Props
  • 类型检查

准备

我们直接用上一节中的 react-demo-day5 项目来作为我们的 Demo 项目,还没有创建的小伙伴可以直接执行以下命令 clone 一份代码:

git clone -b dev https://gitee.com/vv_bug/react-demo-day5.git

接着进入到项目根目录 react-demo-day5 ,并执行以下命令来安装依赖与启动项目:

npm install --registry https://registry.npm.taobao.org && npm start
1-1.png

等项目打包编译成功,浏览器会自动打开项目入口,看到上面截图的效果的时候,我们的准备工作就完成了。

性能优化

UI 更新需要昂贵的 DOM 操作,而 React 内部使用几种巧妙的技术以便最小化 DOM 操作次数。对于大部分应用而言,使用 React 时无需专门优化就已拥有高性能的用户界面。尽管如此,你仍然有办法来加速你的 React 应用。

shouldComponentUpdate 的作用

我们先贴一张官方的组件生命周期图:

1-2.png

可以看到,我们可以根据组件的 shouldComponentUpdate 方法去控制是否继续往下走 render 方法,以及后面的 节点 diff 对比,最后更新 DOM 节点等操作。

可以看到,我们在平时项目中,要尽量避免不必要的 render 操作,我们可以利用组件的 shouldComponentUpdate 方法去控制。

我们在 src/advanced-guides 目录下创建一个 optimizing 目录:

mkdir ./src/advanced-guides/optimizing

然后在 src/advanced-guides/optimizing 目录下创建一个 index.tsx 文件:

import React, {useState} from "react";
import HobbiesCom from "./hobbies.com";

function Optimizing() {
  // 输入框 ref 引用
  let inputRef = React.createRef<HTMLInputElement>();
  // 额外的 state 数据
  let [count, setCount] = useState<number>(0);
  // hobbies 数据
  let [hobbies] = useState<Array<string>>(["打游戏", "做美食"]);

  /**
   * 添加 hobby
   */
  function handleAddHobby() {
    if (inputRef.current) {
      setCount(++count);
    }
  }

  return (
    <div>
      <input placeholder="请输入你的爱好" ref={ inputRef }/>
      <button onClick={ handleAddHobby }>添加</button>
      {/* hobby 列表组件 */ }
      <HobbiesCom hobbies={ hobbies }/>
      { count }
    </div>
  );
}

export default Optimizing;

可以看到,我们简单创建了一个输入框,然后通过 “添加” 按钮修改了 count 的值,而 count 是我们额外定义的一个 State 数据。

接着我们在 src/advanced-guides/optimizing 目录下创建一个 hobbies.com.tsx 组件:

import React from "react";

export type Prop = {
    hobbies: Array<string>,
};

class HobbiesCom extends React.Component<Prop> {
    state = {
        choosed: -1
    }

    handleClick(index: number) {
        this.setState({
            choosed: index
        });
    }

    render() {
        return (
            <ul>
                {this.props.hobbies.map((hobby, index) => (
                    <li
                        style={this.state.choosed === index ? {backgroundColor: "red"} : undefined}
                        key={`hobby-${hobby}-index`}
                        onClick={this.handleClick.bind(this, index)}
                    >
                        {hobby}
                    </li>
                ))}
            </ul>
        );
    }
}

export default HobbiesCom;

可以看到,我们在 HobbiesCom 组件中渲染了 hobbies 的数据。

我们重新运行项目看结果:

npm start
1-3.gif

可以看到,即使我们没有修改 HobbiesCom 组件中的 habbies 属性值,但是 HobbiesCom 组件仍然走了 render 函数,我们希望的是当 habbies 数组的数据没有变化的时候, HobbiesCom 组件不需要重新进行渲染,那我们该怎么做呢?

我们修改一下 src/advanced-guides/optimizing/hobbies.com.tsx 组件:

import React from "react";

export type Prop = {
  hobbies: Array<string>,
};

class HobbiesCom extends React.Component<Prop> {
  state = {
    choosed: -1
  }

  shouldComponentUpdate(nextProps: Prop, nextState: any) {
    // 浅对比新老属性的 hoobies 变量跟新老 state 的 choosed 属性
    return nextProps.hobbies !== this.props.hobbies || this.state.choosed !== nextState.choosed;
  }

 ...
}

export default HobbiesCom;

[图片上传失败...(image-906254-1617350228836)]

可以看到,只有当 shouldComponentUpdate 方法返回是 true 的时候,组件才会走 render 方法,我们对属性跟 State 做了一个浅对比,所以避免了不必要 render 方法的执行。

其实在 React 中,提供了一个 PureComponent 组件,它的作用就是在 shouldComponentUpdate 方法中对属性跟 State 进行了一个浅对比。

我们可以用 PureComponent 组件改造一下 src/advanced-guides/optimizing/hobbies.com.tsx 组件:

import React from "react";

export type Prop = {
  hobbies: Array<string>,
};

class HobbiesCom extends React.PureComponent<Prop> {
  state = {
    choosed: -1
  }

  handleClick(index: number) {
    this.setState({
      choosed: index
    });
  }

  render() {
    return (
      <ul>
        { this.props.hobbies.map((hobby, index) => (
          <li
            style={ this.state.choosed === index ? {backgroundColor: "red"} : undefined }
            key={ `hobby-${ hobby }-index` }
            onClick={ this.handleClick.bind(this, index) }
          >
            { hobby }
          </li>
        )) }
      </ul>
    );
  }
}

export default HobbiesCom;

可以看到,我们只需要把继承的 React.Component 改成 React.PureComponent 即可,效果跟上面的一样,我就不演示了哈,小伙伴自己跑跑。

React.memo

我们上面说的都是 “类组件” 中的性能优化,在 “类组件” 中我们可以通过自定义 shouldComponentUpdate 方法或者 React.PureComponent 来减少不必要的 render 操作,但是在 “函数式组件” 中我们该怎么做呢?

React17+ ,官方已经给给我们提供了一个高阶组件 React.memo

我们直接在 src/advanced-guides/optimizing 目录下创建一个 hobbies.func.tsx 函数式组件:

import React, {useState} from "react";

export type Prop = {
  hobbies: Array<string>,
};

function HobbiesFunc(props: Prop) {
  let [choosed, setChoosed] = useState(-1);

  function handleClick(index: number) {
    setChoosed(index);
  }

  return (
    <ul>
      { props.hobbies.map((hobby, index) => (
        <li
          style={ choosed === index ? {backgroundColor: "red"} : undefined }
          key={ `hobby-${ hobby }-index` }
          onClick={ () => {
            handleClick(index);
          } }
        >
          { hobby }
        </li>
      )) }
    </ul>
  );
}

function areEqual(prevProps: Prop, nextProps: Prop) {
  /*
    如果把 nextProps 传入 render 方法的返回结果与
    prevProps 传入 render 方法的返回结果一致则返回 true,
    否则返回 false
    */
  return prevProps.hobbies === nextProps.hobbies;
}

export default React.memo<Prop>(HobbiesFunc, areEqual);

可以看到,我们通过 React.memo 高阶组件中的第二个参数 areEqual 方法中做了判断,当新老 Props 中的 habbies 数据相等的时候,不执行组件的 render 操作。

React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useStateuseReduceruseContext 的 Hook,当 context 发生变化时,它仍会重新渲染。

默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现,所以我们 Demo 中其实不需要定义第二个参数,默认就可以达到我们想要的效果了。

效果就不演示啦,跟前面的类组件一致,小伙伴自己跑跑看效果哦。

我们分别通过 Demo 演示了 “类组件” 跟 “函数式组件” 中的性能优化,小伙伴一定要自己打断点跑跑看效果哦。

Portals

Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。

ReactDOM.createPortal(child, container)

第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。第二个参数(container)是一个 DOM 元素。

通常来讲,当你从组件的 render 方法返回一个元素时,该元素将被挂载到 DOM 节点中离其最近的父节点:

render() {
  // React 挂载了一个新的 div,并且把子元素渲染其中
  return (
    <div>      {this.props.children}
    </div>  );
}

然而,有时候将子元素插入到 DOM 节点中的不同位置也是有好处的:

render() {
  // React 并*没有*创建一个新的 div。它只是把子元素渲染到 `domNode` 中。
  // `domNode` 是一个可以在任何位置的有效 DOM 节点。
  return ReactDOM.createPortal(
    this.props.children,
    domNode  );
}

一个 portal 的典型用例是当父组件有 overflow: hiddenz-index 样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框。

我们还是通过 Demo 来演示一下。

首先在 src/advanced-guides 目录下创建一个 portals 目录:

mkdir ./src/advanced-guides

接着我们在 src/advanced-guides/portals 目录下创建一个组件 index.tsx

import React, {useState} from "react";
import Model from "./model";

function Portals() {
    let [isShowModel, setShowModel] = useState(false);

    function handleShow() {
        if (!isShowModel) {
            setTimeout(() => {
                setShowModel(false);
            }, 1000);
        }
        setShowModel((show) => {
            return !show;
        });
    }

    return (
        <React.Fragment>
            {isShowModel && (
                <Model>
                    <div className="model">hello model</div>
                </Model>
            )}
            <div onClick={handleShow}>点我 show model</div>
        </React.Fragment>
    );
}

export default Portals;

可以看到,我们用了一个 isShowModel 状态去控制 Model 组件的展示。

然后我们在 src/advanced-guides/portals 目录下创建一个 model.tsx 组件:

import ReactDOM from "react-dom";

function Model(props: any) {
  return ReactDOM.createPortal(props.children, document.body);
}

export default Model;

很简单,我们直接用 ReactDOM.createPortal 定义并返回了一个 React 元素,并且把 Model 组件的子元素都绑定到了 document.body 节点上了。

接着我们去 src/main.scss 样式文件中添加一个 model 样式:

.root{
  font-size: 16px;
}
.model{
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  width: 200px;
  height: 200px;
  margin: auto;
  line-height: 200px;
  text-align: center;
  background-color: rgba(0,0,0,0.7);
}

我们重新运行项目看结果:

1-5.gif

可以看到,当我们点击 “show model” 按钮的时候,页面中显示了我们 Model 组件的子元素,并且这些子元素最后都被挂载到了 document.body 节点上了。

下面我们换一种实现方式,我们来实现一个 Toast 组件,让它能像 Model 组件一样,挂载到 body 节点上,然后指定时间后自动消失。

我们在 src/advanced-guides/portals 目录底下创建一个 toast.tsx 组件:

import ReactDom from "react-dom";
import React from "react";

class Toast {
    static divEle: HTMLDivElement | null;

    static show(msg: string, timing: number = 1000) {
        Toast.hide();
        Toast.divEle = document.createElement("div");
        ReactDom.render(
            (<div className="model">{msg}</div>),
            Toast.divEle
        );
        document.body.appendChild(Toast.divEle);
        setTimeout(() => {
            Toast.hide();
        }, timing);
    }

    static hide() {
        Toast?.divEle && document.body.removeChild(Toast.divEle);
        Toast.divEle = null;
    }
}

export default Toast;

可以看到,我们用 ReactDom.render 创建了一个 React 根元素,把 Toast 的内容挂载到了一个 div 元素中,接着又把这个 div 元素挂在到了 document.body 节点上了。

我们修改一下 src/advanced-guides/portals/index.tsx 组件,用一下我们的 Toast 组件:

import React, {useState} from "react";
import Model from "./model";
import Toast from "./toast";

function Portals() {
    let [isShowModel, setShowModel] = useState(false);

    function handleShow() {
        if (!isShowModel) {
            setTimeout(() => {
                setShowModel(false);
            }, 1000);
        }
        setShowModel((show) => {
            return !show;
        });
    }

    function apiShowModel() {
      Toast.show("你好呀", 2000);
    }

    return (
        <React.Fragment>
            {isShowModel && (
                <Model>
                    <div className="model">hello model</div>
                </Model>
            )}
            <div onClick={handleShow}>点我 show model</div>
            <div onClick={apiShowModel}>Api Show</div>
        </React.Fragment>
    );
}

export default Portals;

可以看到,我们在 apiShowModel 方法中调用了 Toast.show("你好呀", 2000) 方法创建了个一个 Toast

我们重新运行项目看结果:

1-6.gif

可以看到,跟前面 Model 组件的效果一致。

下面我们用 ReactDom.createPortal 改造一下 Toast 组件:

import ReactDom from "react-dom";
import React from "react";

class Toast {
    static divEle: React.RefObject<HTMLDivElement> | null;

    static show(msg: string, timing: number = 1000) {
        Toast.hide();
        Toast.divEle = React.createRef<HTMLDivElement>();
        ReactDom.render(
            ReactDom.createPortal((<div ref={Toast.divEle} className="model">{msg}</div>), document.body),
            document.createElement("div")
        );
        setTimeout(() => {
            Toast.hide();
        }, timing);
    }

    static hide() {
        Toast?.divEle?.current && document.body.removeChild(Toast.divEle.current);
        Toast.divEle = null;
    }
}

export default Toast;

可以看到,我们把之前的 document.body.appendChild 改成了 ReactDom.createPortal,效果跟前面的一致,我就不演示啦,很明显了吧,小伙伴有没有弄懂 ReactDom.createPortal 的原理呢?

Render Props

术语 “render prop” 是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术,在 vueAngular 中叫 “插槽”。

具有 render prop 的组件接受一个返回 React 元素的函数,并在组件内部通过调用此函数来实现自己的渲染逻辑。

说起来抽象,我们还是通过 Demo 来分析一下。

比如我们现在有一个组件支持 “头部”、“内容”、“尾部” 的展示,其中 “头部”、“尾部” 支持自定义方式。

我们首先在 src/advanced-guides 目录下创建一个 render-props 目录:

mkdir ./src/advanced-guides/render-props

然后我们在 src/advanced-guides/render-props 目录下创建一个 index.tsx 组件:

import PropTypes from "prop-types";

type Prop = {
    renderHeader: () => React.ElementType,
    renderFooter: () => React.ElementType,
};

function RenderProp(props: Prop | undefined) {
    return (
        <div style={{backgroundColor: "darkcyan"}}>
            {/*  渲染头部  */}
            {props?.renderHeader()}
            {/*  渲染内容  */}
            <div>我是 RenderProps 组件内容模块</div>
            {/*  渲染尾部  */}
            {props?.renderFooter()}
        </div>
    );
}

RenderProp.propTypes = {
    renderHeader: PropTypes.func,
    renderFooter: PropTypes.func,
};
RenderProp.defaultProps = {
    renderHeader: () => (<div>我是默认头部内容</div>),
    renderFooter: () => (<div>我是默认尾部内容</div>),
};
export default RenderProp;

可以看到,我们创建了一个 RenderProp 组件,RenderProp 组件中渲染了三个部分 “头部”、“内容”、“尾部”,并且 “头部”、“尾部” 支持自定义。

我们修改一下 src/advanced-guides/index.tsx 组件,引入 RenderProp 组件:

/**
 * 核心概念列表
 */
import CodeSplit from "./code-split";
import Context from "./context";
import ErrorBoundaries from "./error-boundaries";
import ErrorCom from "./error";
import ForwardRef from "./forward-ref";
import Optimizing from "./optimizing";
import Portals from "./portals";
import RenderProps from "./render-props";
function AdvancedGuides() {
  return (
    <ErrorBoundaries>
      <div>
        {/* 代码分割 */ }
        <CodeSplit/>
        {/* Context */ }
        <Context/>
        {/* 报错的组件 */ }
        <ErrorCom/>
        {/* Refs 转发 */ }
        <ForwardRef/>
        {/* 性能优化 */ }
        <Optimizing/>
        {/* Portals */ }
        <Portals/>
        {/* Render Props */ }
        <RenderProps/>
      </div>
    </ErrorBoundaries>
  );
};
export default AdvancedGuides;

然后我们重新运行项目看结果:

1-7.png

可以看到,RenderProp 组件渲染了默认的内容。

接下来我们修改一下 src/advanced-guides/index.tsx 组件,自定义 RenderProps 的 “头部” 跟 “尾部” 内容:

/**
 * 核心概念列表
 */
import CodeSplit from "./code-split";
import Context from "./context";
import ErrorBoundaries from "./error-boundaries";
import ErrorCom from "./error";
import ForwardRef from "./forward-ref";
import Optimizing from "./optimizing";
import Portals from "./portals";
import RenderProps from "./render-props";

function AdvancedGuides() {
    return (
        <ErrorBoundaries>
            <div>
                {/* 代码分割 */}
                <CodeSplit/>
                {/* Context */}
                <Context/>
                {/* 报错的组件 */}
                <ErrorCom/>
                {/* Refs 转发 */}
                <ForwardRef/>
                {/* 性能优化 */}
                <Optimizing/>
                {/* Portals */}
                <Portals/>
                {/* Render Props */}
                <RenderProps
                    renderHeader={() => (<div>我是自定义头部内容</div>)}
                    renderFooter={() => (<div>我是自定义尾部内容</div>)}
                />
            </div>
        </ErrorBoundaries>
    );
};
export default AdvancedGuides;

然后重新运行项目看结果:

1-8.png

可以看到,我们成功的自定义了 RenderProp 组件的头部跟尾部内容。

有小伙伴要疑问了 “这样做的目的是什么呢?”我举个例子吧:

比如你的领导需要你开发一个 RenderProp 组件,告诉你需要支持 “头部”、“内容”、“尾部” 的渲染。ok,你能力很强,很快就完成了领导的需求,你开心的去休假去了,然后你领导把这个组件直接给到了另外一个开发手中,领导心血来潮了,说 “头部内容样式需要改改,赶紧把那个休假的人叫回来!!”,哈哈,这个时候你是不是就很无语了呢?那如果你的组件支持自定义功能,你就可以很牛逼的告诉领导:“ 不想用默认样式的话,支持自定义头部跟尾部的,爱怎么玩就怎么玩”。

使用 PropTypes 进行类型检查

随着你的应用程序不断增长,你可以通过类型检查捕获大量错误。对于某些应用程序来说,你可以使用 FlowTypeScript 等 JavaScript 扩展来对整个应用程序做类型检查。但即使你不使用这些扩展,React 也内置了一些类型检查的功能。要在组件的 props 上进行类型检查,你只需配置特定的 propTypes 属性,比如我们上面的 RenderProp 组件:

import PropTypes from "prop-types";

type Prop = {
    renderHeader: () => React.ElementType,
    renderFooter: () => React.ElementType,
};

function RenderProp(props: Prop | undefined) {
    return (
      ...
    );
}

RenderProp.propTypes = {
    renderHeader: PropTypes.func,
    renderFooter: PropTypes.func,
};
RenderProp.defaultProps = {
    renderHeader: () => (<div>我是默认头部内容</div>),
    renderFooter: () => (<div>我是默认尾部内容</div>),
};
export default RenderProp;

我们使用了 ts 静态类型校验跟 prop-types 的动态校验,如果在使用组件的时候不按规定传递属性类型的话,开发模式中直接就会报错了。

我们修改一下 src/advanced-guides/index.tsx 文件:

{/* Render Props */}
  <RenderProps
    renderHeader={1}
    renderFooter={() => (<div>我是自定义尾部内容</div>)}
  />
1-10.png

可以看到,首先是 IDE 报错了,说我们需要的是 function 类型,但是你传递的是 number 类型,Webpack 编译也直接提示报错了。

接着是页面中的提示 :

1-9.png

PropTypes

以下提供了使用不同验证器的例子:

import PropTypes from 'prop-types';

MyComponent.propTypes = {
  // 你可以将属性声明为 JS 原生类型,默认情况下
  // 这些属性都是可选的。
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,

  // 任何可被渲染的元素(包括数字、字符串、元素或数组)
  // (或 Fragment) 也包含这些类型。
  optionalNode: PropTypes.node,

  // 一个 React 元素。
  optionalElement: PropTypes.element,

  // 一个 React 元素类型(即,MyComponent)。
  optionalElementType: PropTypes.elementType,

  // 你也可以声明 prop 为类的实例,这里使用
  // JS 的 instanceof 操作符。
  optionalMessage: PropTypes.instanceOf(Message),

  // 你可以让你的 prop 只能是特定的值,指定它为
  // 枚举类型。
  optionalEnum: PropTypes.oneOf(['News', 'Photos']),

  // 一个对象可以是几种类型中的任意一个类型
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ]),

  // 可以指定一个数组由某一类型的元素组成
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

  // 可以指定一个对象由某一类型的值组成
  optionalObjectOf: PropTypes.objectOf(PropTypes.number),

  // 可以指定一个对象由特定的类型值组成
  optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
  }),

  // An object with warnings on extra properties
  optionalObjectWithStrictShape: PropTypes.exact({
    name: PropTypes.string,
    quantity: PropTypes.number
  }),

  // 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
  // 这个 prop 没有被提供时,会打印警告信息。
  requiredFunc: PropTypes.func.isRequired,

  // 任意类型的必需数据
  requiredAny: PropTypes.any.isRequired,

  // 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
  // 请不要使用 `console.warn` 或抛出异常,因为这在 `oneOfType` 中不会起作用。
  customProp: function(props, propName, componentName) {
    if (!/matchme/.test(props[propName])) {
      return new Error(
        'Invalid prop `' + propName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  },

  // 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。
  // 它应该在验证失败时返回一个 Error 对象。
  // 验证器将验证数组或对象中的每个值。验证器的前两个参数
  // 第一个是数组或对象本身
  // 第二个是他们当前的键。
  customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
    if (!/matchme/.test(propValue[key])) {
      return new Error(
        'Invalid prop `' + propFullName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  })
};

限制单个元素

你可以通过 PropTypes.element 来确保传递给组件的 children 中只包含一个元素。

import PropTypes from 'prop-types';

class MyComponent extends React.Component {
  render() {
    // 这必须只有一个元素,否则控制台会打印警告。
    const children = this.props.children;
    return (
      <div>
        {children}
      </div>
    );
  }
}

MyComponent.propTypes = {
  children: PropTypes.element.isRequired
};

默认 Prop 值

您可以通过配置特定的 defaultProps 属性来定义 props 的默认值:

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

// 指定 props 的默认值:
Greeting.defaultProps = {
  name: 'Stranger'
};

// 渲染出 "Hello, Stranger":
ReactDOM.render(
  <Greeting />,
  document.getElementById('example')
);

如果你正在使用像 transform-class-properties 的 Babel 转换工具,你也可以在 React 组件类中声明 defaultProps 作为静态属性。此语法提案还没有最终确定,需要进行编译后才能在浏览器中运行。要了解更多信息,请查阅 class fields proposal

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

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

defaultProps 用于确保 this.props.name 在父组件没有指定其值时,有一个默认值。propTypes 类型检查发生在 defaultProps 赋值后,所以类型检查也适用于 defaultProps

函数组件

如果你在常规开发中使用函数组件,那你可能需要做一些适当的改动,以保证 PropsTypes 应用正常。

假设你有如下组件:

export default function HelloWorldComponent({ name }) {
  return (
    <div>Hello, {name}</div>
  )
}

如果要添加 PropTypes,你可能需要在导出之前以单独声明的一个函数的形式,声明该组件,具体代码如下:

function HelloWorldComponent({ name }) {
  return (
    <div>Hello, {name}</div>
  )
}

export default HelloWorldComponent

接着,可以直接在 HelloWorldComponent 上添加 PropTypes:

import PropTypes from 'prop-types'

function HelloWorldComponent({ name }) {
  return (
    <div>Hello, {name}</div>
  )
}

HelloWorldComponent.propTypes = {
  name: PropTypes.string
}

export default HelloWorldComponent

上面的这些内容我们在前面的 Demo 中都有演示过,我们就不再演示了,小伙伴自己多敲敲哦!

总结

ok,React 的高级指引部分我们就算是分析完毕了,认认真真看到这里的小伙伴想必搞定面试跟简单的 React 项目应该是问题不大了,后面章节我们将会会介绍 React 中的 Hook、“全家桶”、“源码分析” 等等。

这节到这就结束啦,下节见~

欢迎志同道合的小伙伴一起交流,一起学习。

Demo 项目全部代码:https://gitee.com/vv_bug/react-demo-day5/tree/dev/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,757评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,478评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,540评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,593评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,903评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,329评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,659评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,383评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,055评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,337评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,864评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,227评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,820评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,999评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,750评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,365评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,260评论 2 258

推荐阅读更多精彩内容