Redian新闻
>
写出易维护的代码|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 componentexport default function ImageList({ images, onClick }{ return images.map((img, i) => <img src={img} key={i} onClick={onClick} />);}
// Container componentexport 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/

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
架构师应该遵守的设计原则球道混入“狸”,“狸”中混入“鯉”西雅图年度房市总结|Redmond 最受欢迎,Bothell、Issaquah 成为后起之秀贏碾壓、輸有病热门方向Top4:大前端监控、移动端性能与效率优化、团队可持续发展、低代码|GMTC 北京站圆满落幕有时我担心我的代码会让 TypeScript 开发者愤怒地退出东胜物联发布AIoT开发者计划,将赋能和服务国内10000个网关开发者这代码居然有差别?CPU友好的代码该这样写如何写出一手让同事膜拜的漂亮代码?唯一维护 API 的普通工程师轻松搞垮 Twitter,马斯克:我们重写代码吧这几天看到好几个网友问到站姿腹肌轮的做法情况,我发一个视频吧。围观:不良资产处置十六式及入行不良行业的创收方式2022房市总结|Redmond 最受欢迎,Bothell、Issaquah 成为后起之秀历年IPO被否项目及原因汇编(自2001至今案例580+)一文详解|如何写出优雅的代码又有A股及原实控人被立案!日本这家“魔法”零食店,斩获世界设计大奖,一切设计都为维护孩子们内心的柔软看了我写的设计模式,全公司同事都开始悄悄模仿了。。。从 4 个方面,写出整洁的代码元宇宙开放平台「大有」进行数千万元新一轮融资,小米联创加码|36氪首发《易经》和乾卦政策解码|什么是“完整社区”?如何补齐设施和服务短板?神鹰球,信天翁,凤凰球,可遇不可求!ChatGPT竟写出毁灭人类计划书,还给出相应Python代码,网友:AI正在指数级发展西雅图看房日记|Redmond 6房10分自住学区房我,开发者,在元宇宙里有一行自己的代码!谁动了我的代码:代码混淆剖析17岁高三女生在“元宇宙”设计虚拟时装,女明星竟穿上她的设计?政策解码|聚焦“托育难”,新措施多管齐下为行业纾困政策解码|更好发挥外资制造业的积极作用GitHub Copilot代码笔刷火了,一刷修bug加文档,特斯拉前AI总监:我现在80%的代码由AI完成"𝙇𝙚𝙖𝙙 𝙏𝙝𝙚 𝘾𝙝𝙖𝙧𝙜𝙚"广告#创译挑战真正人人可用的RPA:实在智能全网首发IPA模式及智能屏幕语义理解技术BigHat Biosciences:新技术能否解锁生物治疗密码|前哨科技特训营独揽国内九成订单,拿下PICO华为,七鑫易维如何将眼动追踪做到第一?
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。