前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
原理篇
面经篇
AI 面试
自检篇
每日一题
  • 综合
    • 综合题型
    • 其他问题
    • 设计模式
    • 思维导图
    • 学习路线
  • 前端基础
    • HTTP
    • 浏览器
    • 计算机基础
  • 进阶学习
    • NPM工作流
    • Docker
    • Canvas
    • Node学习指南
    • 前端综合文章
  • 其他
    • Handbook
    • 职场话题
    • CSS可视化
小程序题库
公众号动态
博客动态
AI 热点
开发者导航
基础篇
进阶篇
高频篇
精选篇
手写篇
原理篇
面经篇
AI 面试
自检篇
每日一题
  • 综合
    • 综合题型
    • 其他问题
    • 设计模式
    • 思维导图
    • 学习路线
  • 前端基础
    • HTTP
    • 浏览器
    • 计算机基础
  • 进阶学习
    • NPM工作流
    • Docker
    • Canvas
    • Node学习指南
    • 前端综合文章
  • 其他
    • Handbook
    • 职场话题
    • CSS可视化
小程序题库
公众号动态
博客动态
AI 热点
开发者导航

React设计模式和最佳实践总结

首页2019-08-10 18:50:12Front-End
JavaScriptReact

# 一、组件实践

# 1.1 设计原则

  • 保持接口小,props 数量要少
  • 根据数据边界来划分组件,充分利用组合
  • 把 state 往上层组件提取,让下层组件只需要实现为纯函数

# 1.2 组件划分

任何一个复杂组件都是从简单组件开始的,一开始我们在 render 函数里写的代码不多,但是随着逻辑的复杂,JSX 代码越来越多,于是,就需要拆分函数中的内容

  • 在 React 中,有一个误区,就是把 render 中的代码分拆到多个 renderXXXX 函数中去,比如下面这样
class StopWatch extends React.Component {
  render() {
    const majorClock = this.renderMajorClock();
    const controlButtons = this.renderControlButtons();
    const splitTimes = this.renderSplitTimes();
    
    return (
       <div>
          {majorClock}
          {controlButtons}
          {splitTimes}
       </div>
    );
  }
  
  renderMajorClock() {
     //TODO: 返回数字时钟的JSX
  }
  
  renderControlButtons() {
     //TODO: 返回两个按钮的JSX
  }
  
  renderSplitTimes() {
     //TODO: 返回所有计次时间的JSX
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

用上面的方法组织代码,当然比写一个巨大的 render 函数要强,但是,实现这么多 renderXXXX 函数并不是一个明智之举,因为这些 renderXXXX 函数访问的是同样的 props 和 state,这样代码依然耦合在了一起。更好的方法,是把这些 renderXXXX 重构成各自独立的 React 组件,像下面这样

class StopWatch extends React.Component {
  render() {
    return (
       <div>
          <MajorClock>
          <ControlButtons>
          <SplitTimes>
       </div>
    );
  }
}

const MajorClock = (props) => {
  //TODO: 返回数字时钟的JSX
};

const ControlButtons = (props) => {
  //TODO: 返回两个按钮的JSX
};
  
const SplitTimes = (props) => {
  //TODO: 返回所有计次时间的JSX
}
@前端进阶之旅: 代码已经复制到剪贴板

我们创造了 MajorClock、ControlButtons 和 SplitTimes 这三个组件,目前,我们并不知道它们是否应该有自己的 state,但是从简单开始,首先假设它们没有自己的 state,定义为函数形式的无状态组件

组件 props 的设计

使用 propTypes 来定义组件的 props

const ControlButtons = (props) => {
  //TODO: 返回两个按钮的JSX
};

ControlButtons.propTypes = {
  activated: PropTypes.bool,
  onStart: PropTypes.func.isRquired,
  onPause: PropTypes.func.isRquired,
  onSplit: PropTypes.func.isRquired,
  onReset: PropTypes.func.isRquired,
  splits: PropTypes.arrayOf(PropTypes.number)
};

@前端进阶之旅: 代码已经复制到剪贴板

# 1.3 组件内部实现

  • 尽量每个组件都有自己专属的源代码文件
  • 用解构赋值(destructuring assignment)的方法获取参数 props 的每个属性值
  • 利用属性初始化(property initializer)来定义 state 和成员函数

属性初始化方法

尽量不要在 JSX 中写内联函数(inline function),比如这样写,是很不恰当的

<ControlButtons
  activated={this.state.isStarted}
  onStart={() => { /* TODO */}}
  onPause={() => { /* TODO */}}
  onReset={() => { /* TODO */}}
  onSplit={() => { /* TODO */}}
/>
@前端进阶之旅: 代码已经复制到剪贴板

当然,按照上面那种写法,也可以完成程序的功能,但是,会带来性能的代价。首先,每一次渲染这段 JSX,都会产生全新的函数对象,这是一种浪费;其次,因为每一次传给 ControlButtons 的都是新的 props,这样 ControlButtons 也无法通过 shouldComponentUpdate 对 props 的检查来避免重复渲染

# 二、组件设计模式

# 2.1 高阶组件

  • 在开发 React 组件过程中,很容易发现这样一种现象,某些功能是多个组件通用的,如果每个组件都重复实现这样的逻辑,肯定十分浪费,而且违反了“不要重复自己”(DRY,Don’t Repeat Yourself)的编码原则,我们肯定想要把这部分共用逻辑提取出来重用
  • 我们说过,在 React 的世界里,组件是第一公民,首先想到的是当然是把共用逻辑提取为一个 React 组件。不过,有些情况下,这些共用逻辑还没法成为一个独立组件,换句话说,这些共用逻辑单独无法使用,它们只是对其他组件的功能加强

高阶组件的基本形式

“高阶组件”名为“组件”,其实并不是一个组件,而是一个函数,只不过这个函数比较特殊,它接受至少一个 React 组件为参数,并且能够返回一个全新的 React 组件作为结果,当然,这个新产生的 React 组件是对作为参数的组件的包装,所以,有机会赋予新组件一些增强的“神力”

一个最简单的高阶组件是这样的形式

const withDoNothing = (Component) => {
  const NewComponent = (props) => {
    return <Component {...props} />;
  };
  return NewComponent;
};
@前端进阶之旅: 代码已经复制到剪贴板
  • 上面的函数 withDoNothing 就是一个高阶组件,作为一项业界通用的代码规范,高阶组件的命名一般都带 with 前缀,命名中后面的部分代表这个高阶组件的功能

就如同 withDoNothing 这个名字所说的一样,这个高阶组件什么都没做,但是从中可以看出高阶组件的基本代码套路

  • 高阶组件不能去修改作为参数的组件,高阶组件必须是一个纯函数,不应该有任何副作用。
  • 高阶组件返回的结果必须是一个新的 React 组件,这个新的组件的 JSX 部分肯定会包含作为参数的组件
  • 高阶组件一般需要把传给自己的 props 转手传递给作为参数的组件

用高阶组件抽取共同逻辑

接下来,我们对 withDoNothing 进行一些改进,让它实现“只有在登录时才显示”这个功能

假设我们已经有一个函数 getUserId 能够从 cookies 中读取登录用户的 ID,如果用户未登录,这个 getUserId 就返回空,那么“退出登录按钮“就需要这么写:

const LogoutButton = () => {
  if (getUserId()) {
    return ...; // 显示”退出登录“的JSX
  } else {
    return null;
  }
};
@前端进阶之旅: 代码已经复制到剪贴板

同样,购物车的代码就是这样

const ShoppintCart = () => {
  if (getUserId()) {
    return ...; // 显示”购物车“的JSX
  } else {
    return null;
  }
};

@前端进阶之旅: 代码已经复制到剪贴板

上面两个组件明显有重复的代码,我们可以把重复代码抽取出来,形成 withLogin 这个高阶组件,代码如下

const withLogin = (Component) => {
  const NewComponent = (props) => {
    if (getUserId()) {
      return <Component {...props} />;
    } else {
      return null;
    }
  }

  return NewComponent;
};
@前端进阶之旅: 代码已经复制到剪贴板

如此一来,我们就只需要这样定义 LogoutButton 和 ShoppintCart:

const LogoutButton = withLogin((props) => {
  return ...; // 显示”退出登录“的JSX
});

const ShoppingCart = withLogin(() => {
  return ...; // 显示”购物车“的JSX
});
@前端进阶之旅: 代码已经复制到剪贴板

我们避免了重复代码,以后如果要修改对用户是否登录的判断逻辑,也只需要修改 withLogin,而不用修改每个 React 组件

高阶组件的高级用法

  • 高阶组件只需要返回一个 React 组件即可,没人规定高阶组件只能接受一个 React 组件作为参数,完全可以传入多个 React 组件给高阶组件
  • 比如,我们可以改进上面的 withLogin,让它接受两个 React 组件,根据用户是否登录选择渲染合适的组件
const withLoginAndLogout = (ComponentForLogin, ComponentForLogout) => {
  const NewComponent = (props) => {
    if (getUserId()) {
      return <ComponentForLogin {...props} />;
    } else {
      return <ComponentForLogout{...props} />;
    }
  }
  return NewComponent;
};

@前端进阶之旅: 代码已经复制到剪贴板

有了上面的 withLoginAndLogout,就可以产生根据用户登录状态显示不同的内容

const TopButtons = withLoginAndLogout(
  LogoutButton,
  LoginButton
);

@前端进阶之旅: 代码已经复制到剪贴板

链式调用高阶组件

  • 高阶组件最巧妙的一点,是可以链式调用。
  • 假设,你有三个高阶组件分别是 withOne、withTwo 和 withThree,那么,如果要赋予一个组件 X 某个高阶组件的超能力,那么,你要做的就是挨个使用高阶组件包装,代码如下
const X1 = withOne(X);
const X2 = withTwo(X1);
const X3 = withThree(X2);
const SuperX = X3; //最终的SuperX具备三个高阶组件的超能力
@前端进阶之旅: 代码已经复制到剪贴板

很自然,我们可以避免使用中间变量 X1 和 X2,直接连续调用高阶组件,如下

const SuperX = withThree(withTwo(withOne(X)));
@前端进阶之旅: 代码已经复制到剪贴板

对于 X 而言,它被高阶组件包装了,至于被一个高阶组件包装,还是被 N 个高阶组件包装,没有什么差别。而高阶组件本身就是一个纯函数,纯函数是可以组合使用的,所以,我们其实可以把多个高阶组件组合为一个高阶组件,然后用这一个高阶组件去包装X,代码如下

上面代码中使用的 compose,是函数式编程中很基础的一种方法,作用就是把多个函数组合为一个函数,在很多开源的代码库中都可以看到,下面是一个参考实现

依赖注入

fe
  • 一、组件实践
    • 1.1 设计原则
    • 1.2 组件划分
    • 1.3 组件内部实现
  • 二、组件设计模式
    • 2.1 高阶组件
    • render props 模式
    • 2.2 提供者模式
    • 2.3 组合组件
  • 三、React 单元测试
    • 3.1 测试的目的
  • 四、单元测试
  • 五、React 状态管理
    • 5.1 组件状态
    • 5.2 Mobx 使用模式
    • 5.3 不同方式对比
  • 六、React Router
  • 七、服务器端渲染
    • 7.1 基本套路
    • 7.2 理解 Next.js
  • 八、React 的未来(1): 拥抱异步渲染
  • 九、React 的未来(2):Suspense 带来的异步操作革命
  • 十、函数化的 Hooks

← 小程序蓝牙记录Ionic3与Ionic4变更对比 →