react 组件逻辑复用

2023-10-27

组件逻辑复用

React为什么设计成组件化的形式?其实最大的原因就是为了方便复用。
然而组件的复用虽然方便,逻辑的复用却很麻烦,因为state的存在,逻辑被锁死在组件内部,很难分离出去。

下面以一个可以改变背景色的步进器为例,展示react中常见的三种逻辑复用模式

高阶组件(HOC)实现逻辑复用

import React from "react";

const Count = ({ count, add, minus, theme, changeTheme }) => {
  return (
    <div
      style={{
        backgroundColor: theme,
        flex: 1,
        alignItems: "center",
        justifyContent: "center",
      }}
    >
      <h1>You clicked *{count}* times</h1>
      <h1 onClick={add}>add 1</h1>
      <h1 onClick={minus}>minus 1</h1>
      <h1 onClick={changeTheme}>change background</h1>
    </div>
  );
};

export default Count;

------------------------------------------------------

import React from "react";
import getRandomColor from "../utils/ColorHelper";

const changeTheme = (initColor) => (WrappedComponent) =>
  class ChangeTheme extends React.Component {
    state = {
      theme: initColor,
    };
    changeTheme = () => this.setState({ theme: getRandomColor() });
    render() {
      return (
        <WrappedComponent
          {...this.props}
          theme={this.state.theme}
          changeTheme={this.changeTheme.bind(this)}
        />
      );
    }
  };

export default changeTheme;

------------------------------------------------------

import React from "react";

const countNumber = (initNumber) => (WrappedComponent) =>
  class CountNumber extends React.Component {
    state = { count: initNumber };
    add = () => this.setState({ count: this.state.count + 1 });
    minus = () => this.setState({ count: this.state.count - 1 });
    render() {
      return (
        <WrappedComponent
          {...this.props}
          count={this.state.count}
          add={this.add.bind(this)}
          minus={this.minus.bind(this)}
        />
      );
    }
  };

export default countNumber;

******************************************************

import countNumber from "./HOC/countNumber";
import changeTheme from "./HOC/changeTheme";
import Count from "./HOC/Count";

<!-- 链式调用 -->
const StepperComponent = changeTheme("white")(countNumber(0)(Count));

function App() {
  return (
    <div className="App">
      <StepperComponent />
    </div>
  );
}

高阶组件的原理是这样的。我们定义了两个高阶组件changeTheme、countNumber(其实是一个函数),然后把一个不包含state和逻辑的组件Count(一般称作无状态组件)作为高阶组件的参数传进去,然后返回一个新的class组件。在这个class组件内部,我们实现了计数器的state和逻辑,然后把state和各种函数作为无状态组件的Props传进作为高阶组件参数的无状态组件中去,最后在render里面返回被处理过后的无状态组件

当高阶组件内部的state更新时,由于该state成为了无状态组件的Props,所以就能同时带动内部的无状态组件进行刷新。这样,我们就分离了UI和逻辑,不仅让代码更清晰,而且高阶组件也可以拿给其他的组件反复使用

高阶组件有一个问题是属性被写死了。如果子组件需求的属性名写得不一样,高阶组件就无能为力了。比如上面的Count组件,接受了count,add,minus这个三个属性,但如果另一个组件需要的是num,addNum,minusNum这三个属性呢?两个组件明明需要相同的功能,逻辑却没法在这里复用了

同时虽然可以链式调用,但是链式调用过多的话,会生成很长的异常栈,导致错误难以定位

而且这种方式的确与 render 距离太远了,以至于在 class 内部可能都得纠结一下各个 props 究竟是来自于哪里,如果有问题、问题又出自于哪里。

render props实现逻辑复用

import React from "react";
import getRandomColor from '../utils/ColorHelper'

class ChangeTheme extends React.Component {
  state = { theme: this.props.initColor };
  changeTheme = () => this.setState({ theme: getRandomColor() });
  render() {
    return this.props.render({
      theme: this.state.theme,
      changeTheme: this.changeTheme.bind(this),
    });
  }
}

export default ChangeTheme;

------------------------------------------------------

import React from "react";

class CountNumber extends React.Component {
  state = { count: this.props.initNumber };
  add = () => this.setState({ count: this.state.count + 1 });
  minus = () => this.setState({ count: this.state.count - 1 });
  render() {
    return this.props.render({
      count: this.state.count,
      add: this.add.bind(this),
      minus: this.minus.bind(this),
    });
  }
}

export default CountNumber;

------------------------------------------------------

import React from "react";
import CountNumber from "./CountNumber";
import ChangeTheme from "./ChangeTheme";

const Stepper = () => {
  return (
    <ChangeTheme
      initColor={"white"}
      render={({ theme, changeTheme }) => (
        <div
          style={{
            backgroundColor: theme,
            flex: 1,
            alignItems: "center",
            justifyContent: "center",
          }}
        >
          <CountNumber
            initNumber={0}
            render={({ count, add, minus }) => {
              // 属性名不同不影响逻辑复用
              const num = count;
              const addNum = add;
              const minusNum = minus;
              return (
                <>
                  <h1>You clicked *{num}* times</h1>
                  <h1 onClick={addNum}>add 1</h1>
                  <h1 onClick={minusNum}>minus 1</h1>
                  <h1 onClick={changeTheme}>change background</h1>
                </>
              );
            }}
          ></CountNumber>
        </div>
      )}
    ></ChangeTheme>
  );
};
export default Stepper;

******************************************************

import "./App.css";
import Stepper from "./RenderProps/Stepper";

function App() {
  return (
    <div className="App">
      <Stepper />
    </div>
  );
}

export default App;

render props模式会假设子组件是一个函数,并把当前组件内部的state和逻辑传入该函数中。而我们真正想要渲染的无状态组件,则可以通过这个函数的参数得到它想要的属性
在render props模式下,属性名不同导致的逻辑不能复用,就能在这里完美得到解决

但render props很容易造成组件之间的嵌套严重

Hooks实现逻辑复用

import useCount from './useCount'
import useTheme from './useTheme'

const HookCount = () => {
  const [count, addCount, minusCount] = useCount(0);
  const [theme,changeBackgroundColor] = useTheme('white');
  return (
    <div style={{ backgroundColor:theme, flex: 1, alignItems: "center", justifyContent: "center" }}>
      <h1>You clicked *{count}* times</h1>
      <h1 onClick={addCount}>add 1</h1>
      <h1 onClick={minusCount}>minus 1</h1>
      <h1 onClick={changeBackgroundColor}>change background</h1>
    </div>
  );
};
export default HookCount;

------------------------------------------------------

import { useState } from "react";

const useCount = (initNumber) => {
  const [count, setCount] = useState(initNumber);
  const addCount = () => setCount(count + 1);
  const minusCount = () => setCount(count - 1);
  return [count, addCount, minusCount];
};

export default useCount;

------------------------------------------------------

import { useState } from "react";
import getRandomColor from "../utils/ColorHelper";

const useTheme = (initColor) => {
  const [theme, changeTheme] = useState(initColor);
  const changeBackgroundColor = () => changeTheme(getRandomColor());
  return [theme, changeBackgroundColor];
};

export default useTheme;

******************************************************

import "./App.css";
import Count from "./Hooks/Count";

function App() {
  return (
    <div className="App">
      <Count />
    </div>
  );
}

export default App;

在hooks中,可以将一个组件中需要的逻辑,也就是state单独抽离,这样也显得语义更清晰、结构更优雅,代码量大大减少

github地址:
https://github.com/huziiijian/components_structure

参考资料:

https://zhuanlan.zhihu.com/p/62791765?utm_source=com.gzqwkj.cshu
https://blog.csdn.net/qq_40962320/article/details/87043581
https://react.docschina.org/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

react 组件逻辑复用 的相关文章

随机推荐

  • JAVA用线程池模拟查询大批量数据

    JAVA用线程池模拟查询大批量数据 文章目录 JAVA用线程池模拟查询大批量数据 1 前言背景 1 1 线程 1 2 进程与线程的区别总结 1 3 使用多线程的好处 2 多线程的实现 2 1 操作流程 3 测试 1 前言背景 1 1 线程
  • QListWidget右键菜单

    关于QListWidget右菜单的的实现 网上多数资料都没有提到如何使用Qt Creator快速实现 如参考资料 1 2 本文重点介绍此方法 1 槽函数生成 通过Qt Creator的UI设计器将QListWidget控件拖放到主界面中 然
  • 刷题之77. 组合

    题目 给定两个整数 n 和 k 返回范围 1 n 中所有可能的 k 个数的组合 你可以按 任何顺序 返回答案 示例 1 输入 n 4 k 2 输出 2 4 3 4 2 3 1 2 1 3 1 4 来源 力扣 LeetCode 链接 http
  • Pytorch实现Seq2Seq

    前言 Seq2Seq模型用来处理nlp中序列到序列的问题 是一种常见的Encoder Decoder模型架构 基于RNN同时解决了RNN的一些弊端 输入和输入必须是等长的 Seq2Seq的模型架构可以参考Seq2Seq详解 也可以读论文原文
  • MyBatis中的$和#,你知道他们的区别吗?

    转自 MyBatis中的 和 你知道他们的区别吗 下文笔者将讲述MyBatis中的 和 的区别简介说明 如下所示 在MyBatis的xml配置文件中 我们经常看见 和 后面紧跟变量 那么他们有什么区别呢 下文笔者将一一道来 如下所示 1 是
  • C# 爬虫遇到EventStream数据时该怎么获取值

    声明 本文只作学习研究 禁止用于非法用途 否则后果自负 如有侵权 请告知删除 谢谢 今天调用某个网站的接口时发现数据格式是这种的 第一次遇到 正常的应该是这样的才对 有个 响应 然后响应里面是一些返回过来的数据 而这个就很奇怪 没有 响应
  • 07模板学习之模板类的static数据成员的归属问题

    07模板学习之模板类的static数据成员的归属问题 1 模板类的static数据成员的归属问题分析 从上面的图分析 先看类模板中的static int a 若类模板中声明了static数据 那么该a是属于类模板还是属于具体类呢 假设属于类
  • 菜鸟入门Docker

    菜鸟入门Docker 说明 一 什么是Docker 1 虚拟机和Linux容器 二 Docker用途 三 Docker安装 1 设置仓库 2 安装 Docker Engine Community 3 验证安装成功 四 Docker启动与停止
  • Linux必杀(十八):VI、VIM编辑器

    题记 基本上VI共分为3种模式 分别是一般模式 命令行模式和编辑模式 一 一般模式 以Vi打开一个文件就直接进入一般模式了 在这个模式下 可以使用上下左右按键来移动光标 可以删除字符或删除整行 也可以复制 粘贴文件数据 二 编辑模式 在一般
  • Dubbo中的一些常见问题?

    关于dubbo是用的什么协议 在使用dubbo的时候会配置
  • ubuntu 防火墙基本设置

    查看防火墙状态 ufw status 打开防火墙 sudo ufw enable 重启防火墙 sudo ufw reload 开放指定端口 ufw allow 8080
  • 使用C语言打印不同星号图案(矩形 平行四边形 三角形)

    献给大一或大二的学弟学妹们和在自学 C语言的同志们 打印自定义行数的矩形 打印效果 参考代码 include
  • echarts 图表无数据为空时显示“暂无数据”

    如标题所述 我们希望 echarts 图表在没有数据时显示 暂无相关数据 字样 操作 需要对返回的数据做判断 如果有数据则正常显示图表 如果没有数据 我们将此 div 的内容改为文本 暂无相关数据 并设置样式即可 HTML div div
  • 从感知机到Transformer,一文概述深度学习简史

    关注公众号 发现CV技术之美 本文转自机器之心 作者 Jean de Dieu Nyandwi 机器之心编译 这篇文章从感知机开始 按照时间顺序回顾了深度学习的历史 1958 年 感知机的兴起 1958 年 弗兰克 罗森布拉特发明了感知机
  • Java中的异常

    Java中的异常 1 什么是异常 2 异常的类结构 3 运行时异常的特点 4 编译时异常特点 5 对受检异常进行处理 5 1 try catch 捕获处理 5 2 finally子句处理 5 3 finally子句 5 4 throws抛出
  • java案例之制作系统

    java案例之制作系统 案例 需求 定义一个方法 可以接收中奖号码的数组 用户选号的数组 根据命中红球数和篮球数判断最终的结果并输出 分析 系统需要三部份 第一部分是 生成随机产生的7位数双色球数字 其中前6位是红球 第7位是蓝球 红球范围
  • 使用MySQL Workbench建立数据库,建立新的表,向表中添加数据

    点击上图中的 加号 图标 新建一个连接 如上图 先输入数据库的账号密码 帐号默认为root 填好密码后 点击 OK 连接就建立好了 建立完成后 会出现一个长方形的框框 双击它 出现下图所示页面 点击图中的红圈里的按钮 新建一个Schema
  • 图的深度优先遍历(递归与非递归算法)和广度优先遍历

    老师的题目 实验内容 已知某地区的公路网以图表示 图中的顶点表示站点 任意两站点间的路段以带权的边构成的邻接矩阵表示 矩阵中非零元表示两个站点间存在直接的路段 否则没有路段 打开E Test文件夹中的exp06 cpp文件 补充编写所需代码
  • html做成小程序,微信小程序——简单静态网页的制作

    一 前言 需要知识 HTML CSS 注意 微信小程序的语法与HTML和CSS不太相同 但本质是一样的 要求 进入开发者工具并且创建一个测试小程序 选择建立快速模板 在pages目录底下新建一个first的文件夹 其中包括指定的四个文件 并
  • react 组件逻辑复用

    组件逻辑复用 React为什么设计成组件化的形式 其实最大的原因就是为了方便复用 然而组件的复用虽然方便 逻辑的复用却很麻烦 因为state的存在 逻辑被锁死在组件内部 很难分离出去 下面以一个可以改变背景色的步进器为例 展示react中常