React学习笔记之四---抽离组件

一个复杂的应用都是由简单的应用发展而来的。随着越来越多的功能加入项目,代码会变得越来越难以控制,本文主要探讨在大型项目中如何对组件进行组织,让项目具备可维护性。

组件设计的基本原则

基本原则

单一职责 这原本来源u面向对象编程,规范定义是“一个类应该只有一个发生变化的原因”,说的简单通俗一点就是:一个类只负责一件事情。不管是什么编程范式,只要是模块化的程序设计都使用单一职责原则,在React中,组件就是模块。

单一职责要求将组件限制在一个合适的粒度,这个粒度是比较主观的概念,换句话说’单一’是一个相对的概念。 单一职责并不是追求职责粒度的最小化,粒度最小化是一个极端,可能会导致大量模块,模块离散化也会让项目变得难以管理。单一职责要的是一个适合被复用的粒度

往往一开始我们设计的组件都可能复合多个职责,后来出现了代码重复或者模块边界被打破,我们才会将可重复的代码抽离,随着越来越多的重构和迭代,模块职责可能会越来越趋于单一。

单一职责的收益:

  • 降低组件的复杂度,职责单一组件代码量少,容易被理解,可读性高。
  • 降低对其他组件的耦合,当变更到来时可以降低对其他功能的影响,不至于牵一发而动全身。
  • 提高可复用性,功能越单一可复用性就越高,就比如一些基础组件。

基本技巧

  • 如果组件不需要状态,则使用无状态组件。
  • 性能比较上执行:无状态函数 > 有状态函数 > Class组件
  • 最小化props,不要传递超过要求的props.
  • 如果组件内部存在较多条件控制流,这通常意味着要对组件进行抽取。
  • 不要过早优化,只要组件在当前需求下可被复用,然后随机应变。

组件分类

组件主要可分为:容器组件和展示组件
容器组件和展示组件分离是React开发的重要思想,它影响的React应用项目组织和架构总结如下;

容器组件展示组件
关注点业务UI
数据源状态管理器/后端Props
组件形式高阶组件普通组件
  • 容器组件主要关注业务处理,一般以高阶组件的形式存在。通常从外部数据源(redux这些状态管理器或者直接请求服务端数据)获取数据,然后 组合展示组件来构建完整的视图。
    请添加图片描述
  • 展示组件是一个只关注展示的‘元件’,为了可以在多个地方被复用,它不应该耦合‘业务/功能’,或者说不应该过度耦合。对于展示组件,我们要以一种’第三方组件库’的标准来考虑组件的设计,减少业务的耦合度,考虑各种应用场景,设计好公开的接口。

容器组件和展示组件两者未必是简单的包含和被包含的关系。

按照不同的角度又可以把组件分为:有状态组件和无状态组件,纯组件和非纯组件

类别特征是否是纯组件
有状态组件内部存储状态不一定
无状态状态组件内部不存储状态,完全由外部的props来映射,以函数形式存在

纯组件的 ’纯‘来源于函数式编程,指的是对于一个函数而言,给定相同的输入,它总是返回相同的输出,过程没有副作用,没有额外的状态依赖。对应到React中,纯组件指的是props(严格来说还有state和context,它们也是组件的输入)没有变化,组件的输出就不会变动。
请添加图片描述

和React组件的输入输出模型相比,CycleJS对组件输入/输出的抽象则做的更加彻底,更加函数式。它的组件就是一个普通的函数,只有单向的输入和输出
请添加图片描述
函数式编程和组件式编程思想某舟意义上是一致的,它们都是’组合‘的艺术,一个大的函数可以有多个职责单一函数组合而成。组件也是如此。我们将一个大的组件拆分为子组件,对组件做更细粒度的控制,保持它们的纯净性,让它们的职责更加单一,更加独立,这带来的好处就是可以复用性,可测性和可预测性。

我们可以很容易地保证一个底层组件的纯净性,因为它本来就很简单。但是对于一个复杂的组件树,则需要花心思进行构建,所以就有了’状态管理‘的需求。这些状态管理器通常都在组件树的外部维护一个或者多个状态库,然后通过依赖注入形式,将局部的状态注入到子树中,通过视图和逻辑分离的原则,来维持组件树的纯净性。

Redux就是一个典型的解决方案,在Redux的世界里可以认为一个复杂的组件树就是一颗状态树的映射,只要状态树不变,组件树就不变,Redux建议保持组件的纯净性,将组件状态交给Redux和配套的异步处理工具来维护,这样就将整个应用抽象成一个"单向数据流",这是一种简单的“输入/输出”关系。
请添加图片描述
不管是Cyclejs还是Redux,抽象是需要付出一点代价的,就比如redux代码可能就会很啰嗦,一个复杂的状态树,如果缺乏良好的组织,整个应用就会变得很难理解。

示例

永远不要害怕将组件拆分成更小的组件,让一个个可重用组件在大型应用程序中交付使用的过程中,抽离组件期初可能看起来像又脏又累的活儿,所以有一个好的经验法则:如果UI的一部分被使用了好几次(按钮,面板,头像),或者内部比较复杂的东西(app,FeedStory,评论),一个重用的组件对它阿狸说可以达到最大的发挥空间。

原码

import React from 'react';
import ReactDOM from 'react-dom';
 
function formatDate(date) {
    return date.toISOString();
}
 
function Comment(props) {
    return (
        <div className="Comment">
            <div className="UserInfo">
                <img className="avatar"
                     src={props.author.avatarUrl}
                     alt={props.author.name}
                />
                <div className="UserInfo-name">
                    {props.author.name}
                </div>
            </div>
            <div className="Comment-text">
                {props.text}
            </div>
            <div className="Comment-date">
                {formatDate(props.date)}
            </div>
        </div>
    );
}
 
ReactDOM.render(
    <Comment author={{
        avatarUrl: 'https://ss0.bdstatic.com/7Ls0a8Sm1A5BphGlnYG/sys/portrait/item/3ae1dc06.jpg',
        name: 'zhangyatao'
    }} text={'我的名字叫张亚涛'} date={new Date()}/>,
    document.getElementById('root')
);

它接受 author,textdate作为props,用来描述社交媒体网站上的评论。

抽离

首先我们将提取avatar

function Avatar(props){
	return(
		<img className="Avatar"
			 src={props.user.avatarUrl}
			 alt={props.user.name}
		/>
	);
}

avatar不需要知道它在Comment中呈现。这就是为什么我们给它的prop一个更通用的名称:user而不是author。
我们建议从组件自己的角度命名props,而不是使用它的上下文。
我们现在可以对Commnet组件做一点简化

function Comment(props) {
    return (
        <div className="Comment">
            <div className="UserInfo">
                <Avatar user={props.author} />
                <div className="UserInfo-name">
                    {props.author.name}
                </div>
            </div>
            <div className="Comment-text">
                {props.text}
            </div>
            <div className="Comment-date">
                {formatDate(props.date)}
            </div>
        </div>
    );
}

接下来,我们将提取一个userInfo组件,该组件在用户名称旁边呈现一个avatar:

function UserInfo(props) {
    return (
        <div className="UserInfo">
            <avatar uer={props.user} />
            <div className="UserInfo-name">
                {props.user.name}
            </div>
        </div>
    );
}

这使我们可以进一步简化Comment组件

function Comment(props) {
    return (
       <div className="Comment">
           <UserInfo user={props.author} />
           <div className="Comment-text">
               {props.text}
           </div>
           <div className="Comment-date">
               {formatDate(props.date)}
           </div>
       </div>
    );
}

最终代码如下:

import React from 'react';
import ReactDOM from 'react-dom';
 
function formatDate(date) {
    return date.toISOString();
}
function Avatar(props) {
    return (
        <img className="Avatar"
             src={props.user.avatarUrl}
             alt={props.user.name}
        />
    );
}
function UserInfo(props) {
    return (
        <div className="UserInfo">
            <Avatar user={props.user}/>
            <div className="UserInfo-name">
                {props.user.name}
            </div>
        </div>
    );
}
function Comment(props) {
    return (
        <div className="Comment">
            <UserInfo user={props.author}/>
            <div className="Comment-text">
                {props.text}
            </div>
            <div className="Comment-date">
                {formatDate(props.date)}
            </div>
        </div>
    );
}
ReactDOM.render(
    <Comment author={{
        avatarUrl: 'https://ss0.bdstatic.com/7Ls0a8Sm1A5BphGlnYG/sys/portrait/item/3ae1dc06.jpg',
        name: 'zhangyatao'
    }} text={'我的名字叫张亚涛'} date={new Date()}/>,
    document.getElementById('root')
);
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值