写出易维护的代码|React开发的设计模式及原则

简介: 本文对React社区里出现过的一些设计模式进行了介绍,并讲解了他们遵循的设计原则。

介绍

设计模式是最常见的,通用问题的可复用解决方案的归纳总结,通常被认为是解决该类问题的最佳实践,使用设计模式能帮助我们写出更容易维护,更健壮的代码。设计模式有很多,通常它们都会遵循一些共同的设计原则,接下来我们一起回顾下React社区里出现过的一些设计模式,以及它们所遵循的设计原则。


一些设计原则

1.单一职责原则(Single-responsibility responsibility) : 每个实体(class, function, module)只应该有一个职责。例如当一个组件接收了太多的props,我们应该考虑组件是不是做了太多的事情,有没有必要进行拆分。


2.开闭原则(Open-closed principle):实体(class, function, module) 应该对扩展开放,但是对修改关闭。开闭原则意味着应该存在不直接修改的方式扩展实体的功能。


3.依赖反转原则(Dependency inversion principle):依赖于抽象,而不是具体的实现。依赖注入是一种实现依赖反转的方式。


4.不要自我重复 (Don't repeat yourself):重复代码会造成代码维护的困难。


5.Composition over inheritance[1]: 通过组合集成的两个组件是松耦合关系,通过props来约束。但是有继承关系的两个组件是强耦合关系,对父组件的修改可能会导致子组件的未预期的结果。


React设计模式

Container & presentational component[2]

把业务组件划分成container组件和presentational组件。Presentational component中负责组件的ui渲染,Container component负责数据的获取和事件的响应。


遵循的设计原则:

1.单一职责原则:Presentational component负责ui,Container component负责数据和行为。

2.Don't repeat yourself: Presentational component是纯ui组件,不包含业务逻辑,通常可以被复用。

示例:

import React from "react";

// Presentational component
export default function ImageList({ images, onClick }) {
  return images.map((img, i) => <img src={img} key={i} onClick={onClick} />);
}

// Container component
export default class ImageListContainer extends React.Component {
  constructor() {
    super();
    this.state = {
      images: []
    };
  }
  
  componentDidMount() {
    fetch("https://images.com")
      .then(res => res.json())
      .then(({ images }) => this.setState({ images }));
  }
 
  handleClick() {
    // ...
  }
  
  render() {
    return <ImageList images={this.state.images} onClick={handleClick} />;
  }
}

HOC

Higher-order component 是一个以组件为参数,返回一个新组件的函数,用于复用组件的逻辑,Redux的 connect[3] 和 Relay的createFragmentContainer[4]都有使用HOC模式。


遵循的设计原则:


1.Don't repeat yourself:把可复用的逻辑放到HOC中,实现代码复用。

2.Composition over inheritance: hoc中传入的组件和返回的组件是组合的关系, 也可以把多个HOC进行多次的嵌套组合。

示例:

import React from "react";

export default function withLoader(Component, url) {
  return class HOC extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        loading: true,
        data: {},
      };
    }
   
    componentDidMount() {
      fetch(url)
        .then((res) => res.json())
        .then(({ data }) => this.setState({ data }))
        .finally(() => this.setState({loading: false}))
    }
   
    render() {
      if (this.state.loading) {
        return <div>Loading...</div>;
      }
      return <Component {...this.props} data={this.state.data} />;
    }
  };
}

Render prop

Render prop是指组件的使用者通过组件暴露的函数属性来参与定制渲染相关的逻辑。使用Render prop模式的库包括: React Router[5], Downshift[6] and Formik[7].


遵循的设计原则:


1.Don't repeat yourself:把可复用的逻辑放到组件中,实现代码复用。

2.依赖反转原则:通过render prop注入渲染相关的实现。

3.开闭原则(Open-closed principle):通过render prop暴露扩展点,而不是直接定制组件。

示例:

import React from "react";
class Loader extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      loading: true,
      data: {},
    };
  }
  componentDidMount() {
    fetch(url)
      .then((res) => res.json())
      .then(({ data }) => this.setState({ data }))
      .finally(() => this.setState({ loading: false }));
  }
  render() {
    if (this.state.loading) {
      return <div>Loading...</div>;
    }
    return this.props.renderData(this.state.data);
  }
}

Compound components


Compound components是指通过多个组件的组合来完成特定任务,这些组件通过共享的状态、逻辑进行关联。典型的例子是Select和Select.Option组件。使用Compound components模式的库包括:semantic ui[8];


遵循的设计原则:


1.单一职责原则(Single-responsibility responsibility): 拆分成多个组件,每个组件承担自己的职责。


2.开闭原则(Open-closed principle):需要迭代增强功能时,可以通过创建新的子组件的方式进行扩展。

示例:

import React from "react";
const SelectContext = React.createContext({});

export function Select({ value, onChange, children }) {
  const [open, setOpen] = React.useState(false);
  const [val, setValue] = React.useState(value);

  return (
    <div className={`select`}>
      <div
        className="select-value"
        onClick={() => {
          setOpen(true);
        }}
      >
        {val}
      </div>
      <SelectContext.Provider
        value={{
          value: val,
          setOpen,
          setValue: (newValue) => {
            setValue(newValue);
            if (value !== newValue) {
              onChange(newValue);
            }
          },
        }}
      >
        {open && children}
      </SelectContext.Provider>
    </div>
  );
}

function Option({ children, value }) {
  const {
    setOpen,
    setValue,
    value: selectedValue,
  } = React.useContext(SelectContext);
  return (
    <div
      className={`select-option ${value === selectedValue ? "selected" : ""}`}
      onClick={() => {
        setValue(value);
        setOpen(false);
      }}
    >
      {children}
    </div>
  );
}

function OptionGroup({ children, label }) {
  return (
    <div className="select-option-group">
      <div className="select-option-group-label">{label}</div>
      {children}
    </div>
  );
}

Select.Option = Option;
Select.OptionGroup = OptionGroup;

function Demo() {
  const [city, setCity] = React.useState("北京市");
  return (
    <Select value={city} onChange={setCity}>
      <Select.Option value="北京市">北京市</Select.Option>
      <Select.OptionGroup label="河北省">
        <Select.Option value="石家庄市">石家庄市</Select.Option>
        <Select.Option value="保定市">保定市</Select.Option>
      </Select.OptionGroup>
    </Select>
  );
}


Custom hooks

自定义hooks可以做到把与state和生命周期关联的可复用逻辑封装到独立的函数中, 上面的提及的一些模式都是基于组件的方案,自定义hooks是更细粒度的解决方案。


遵循的设计原则:


1.Don't repeat yourself:把可复用的逻辑放到自定义hooks中,实现代码复用。

2.单一职责原则:每个自定义hooks是都是一个独立的逻辑单元。

示例:

import { useState, useEffect } from "react";
function useLoader(url) {
  const [data, setData] = useState({});
  const [loading, setLoading] = useState(false);
  useEffect(() => {
    setLoading(true);
    fetch(url)
      .then((res) => res.json())
      .then(({ data }) => {
        setData({ data });
      })
      .finally(() => setLoading(false));
  }, [url]);
  return { data, loading };
}


结尾

上面提及的曾经在社区流行的设计模式,往往遵守了一些设计原则,从而能帮助开发者写出健壮,易维护的代码。但是我们需要能根据实际的场景做出判断,是否需要引入这些模式,毕竟还有一个设计原则是YAGNI (You aren't gonna need it)。


参考链接:

[1]https://reactjs.org/docs/composition-vs-inheritance.html

[2]https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0

[3]https://github.com/reduxjs/react-redux/blob/master/docs/api/connect.md#connect

[4]https://relay.dev/docs/v10.1.3/fragment-container/#createfragmentcontainer

[5]https://reactjs.org/docs/composition-vs-inheritance.html

[6]https://reacttraining.com/react-router/web/api/Route/render-func

[7]https://github.com/paypal/downshift

[8]https://github.com/jaredpalmer/formik

[9]https://react.semantic-ui.com/


作者 | 邵杨锋(亦说)

来源 | 阿里云开发者公众号

相关文章
|
4月前
|
资源调度 前端开发 Android开发
如何在 React Native 中进行代码签名验证?
如何在 React Native 中进行代码签名验证?
|
5月前
|
前端开发 JavaScript API
React开发需要了解的10个库
本文首发于微信公众号“前端徐徐”,介绍了React及其常用库。React是由Meta开发的JavaScript库,用于构建动态用户界面,广泛应用于Facebook、Instagram等知名网站。文章详细讲解了Axios、Formik、React Helmet、React-Redux、React Router DOM、Dotenv、ESLint、Storybook、Framer Motion和React Bootstrap等库的使用方法和应用场景,帮助开发者提升开发效率和代码质量。
211 4
React开发需要了解的10个库
|
21天前
|
人工智能 自然语言处理 前端开发
Flame:开源AI设计图转代码模型!生成React组件,精准还原UI+动态交互效果
Flame 是一款开源的多模态 AI 模型,能够将 UI 设计图转换为高质量的现代前端代码,支持 React 等主流框架,具备动态交互、组件化开发等功能,显著提升前端开发效率。
339 1
|
23天前
|
设计模式 架构师 Java
设计模式觉醒系列(01)设计模式的基石 | 六大原则的核心是什么?
本文介绍了设计模式的六大原则,包括单一职责原则(SRP)、开闭原则(OCP)、里氏替换原则(LSP)、接口隔离原则(ISP)、依赖倒置原则(DIP)和迪米特法则。通过具体案例分析了每个原则的应用场景及优势,强调了这些原则在提升代码可维护性、可复用性、可扩展性和降低耦合度方面的重要作用。文章指出,设计模式的核心在于确保系统模块间的低耦合高内聚,并为后续深入探讨23个经典设计模式打下基础。
|
5月前
|
设计模式 算法 搜索推荐
后端开发中的设计模式应用与实践
在软件开发的广袤天地中,后端技术如同构筑高楼大厦的钢筋水泥,支撑起整个应用程序的骨架。本文旨在通过深入浅出的方式,探讨后端开发领域内不可或缺的设计模式,这些模式犹如精雕细琢的工具箱,能够助力开发者打造出既健壮又灵活的系统架构。从单例模式到工厂模式,从观察者模式到策略模式,每一种设计模式都蕴含着深刻的哲理与实践价值,它们不仅仅是代码的组织方式,更是解决复杂问题的智慧结晶。
|
3月前
|
设计模式 Java 程序员
【23种设计模式·全精解析 | 概述篇】设计模式概述、UML图、软件设计原则
本系列文章聚焦于面向对象软件设计中的设计模式,旨在帮助开发人员掌握23种经典设计模式及其应用。内容分为三大部分:第一部分介绍设计模式的概念、UML图和软件设计原则;第二部分详细讲解创建型、结构型和行为型模式,并配以代码示例;第三部分通过自定义Spring的IOC功能综合案例,展示如何将常用设计模式应用于实际项目中。通过学习这些内容,读者可以提升编程能力,提高代码的可维护性和复用性。
【23种设计模式·全精解析 | 概述篇】设计模式概述、UML图、软件设计原则
|
5月前
|
设计模式 算法 数据库连接
PHP中的设计模式:提高代码的可维护性和扩展性
【10月更文挑战第13天】 本文将探讨PHP中常见的设计模式及其在实际项目中的应用。通过对比传统编程方式,我们将展示设计模式如何有效地提高代码的可维护性和扩展性。无论是单例模式确保类的单一实例,还是观察者模式实现对象间的松耦合,每一种设计模式都为开发者提供了解决特定问题的最佳实践。阅读本文后,读者将能更好地理解和应用这些设计模式,从而提升PHP编程的效率和质量。
|
5月前
|
前端开发 JavaScript 开发者
React 组件化开发最佳实践
【10月更文挑战第4天】React 组件化开发最佳实践
104 4
|
5月前
|
设计模式 SQL 安全
PHP中的设计模式:单例模式的深入探索与实践在PHP开发领域,设计模式是解决常见问题的高效方案集合。它们不是具体的代码,而是一种编码和设计经验的总结。单例模式作为设计模式中的一种,确保了一个类仅有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的基本概念、实现方式及其在PHP中的应用。
单例模式在PHP中的应用广泛,尤其在处理数据库连接、日志记录等场景时,能显著提高资源利用率和执行效率。本文从单例模式的定义出发,详细解释了其在PHP中的不同实现方法,并探讨了使用单例模式的优势与注意事项。通过对示例代码的分析,读者将能够理解如何在PHP项目中有效应用单例模式。
|
5月前
|
设计模式 算法 数据库连接
后端开发中的设计模式应用与实践
在软件开发的广袤天地中,设计模式如同夜空中最亮的星辰,引领着开发者们穿越复杂系统的迷雾。本文旨在通过深入浅出的方式,不仅探讨设计模式的理论精髓,揭示它们在后端架构中的重要性,还将以生动的实践案例,展示如何在实际项目中巧妙运用这些模式。我们邀请您一同踏上这场编程之旅,探索如何借助设计模式的力量,让后端系统更加健壮、灵活且易于维护,共同揭开后端技术神秘面纱的一角。

热门文章

最新文章