NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

React 的 10 种迷你开发模式

  • 2019-09-20
  • 本文字数:8800 字

    阅读完需:约 29 分钟

React 的 10 种迷你开发模式

过去几年里,我参与了几个较大的 React 项目,也做了非常多的小项目,在这过程中,我总结了一些 React 常用的开发模式。这些模式是我在 React 入门阶段非常希望看到的,如果你是 React 新手,那你今天算是赚到了,如果你已经是 React 老鸟,不妨看看有哪些模式。本文较长,如果觉得一些介绍比较枯燥(比如 3、6、8、10),可以选择跳过。

1.数据传输

我建议每个 React 初学者都去了解 React 组件向下传递数据(对象、字符串等)的模式,以及传递让子组件传回数据的方法的模式。可以联想救援队将食物与对讲机送抵井底被困矿工。举个例子:



图中左右分别是父组件和子组件,你可以想象连接父子组件的这两个属性允许了数据的双向流通。其实这一条不算真正意义上的开发模式,下面才是。

2.修复 HTML 原生 input

React 和 web 组件的一大好处是,当页面出现 bug,你可以很快定位到问题所在。如果你在考虑页面中使用不同的输入标签,你会发现这些标签的命名大多是无意义,因此,如果我在处理一个包含多个输入的页面时,我首先会处理这个问题



  • 输入应该通过 onChange 方法返回值,而不是通过 Javascript 事件绑定

  • 保证 onChange 返回值的类型与输入的值类型统一,如果 typeofprops.value 是一个 number 类型,应该将 e.target.value 转换成 number 类型再返回

  • 一套 radio 标签和一个 select 标签在功能上都是相同的,唯一的区别只是 UI 的不同,推荐项目中保留一个 组件,可以通过属性 ui=“radio” 或者 ui=“dropDown” 进行控制


以上是我处理原生输入标签时用到的方法,你可以选择其他方式,关键是将这些原生标签转换成为你所用,再也不需要忍受那些糟糕的原生输入标签了。

3.给 input 绑定唯一 ID 的标签

关于 input 输入,如果你注重用户体验,你应该给每个标签绑定一个,通过 id/for 属性进行关联。但如果给每一个 input 都想一个独一无二且生动形象的 ID,那样太浪费时间,而且使用随机生成 ID 的方式也不可行,客户端与服务器端生成的 ID 不同,导致校验不通过,这里推荐你创建一个提供增量 ID 的模块,并在 input 组件中使用,如下所示:


class Input extends React.Component {
constructor(props) {
super(props);
this.id = getNextId();
this.onChange = this.onChange.bind(this);
}
onChange(e) {
this.props.onChange(e.target.value);
}
render() {
return (
<label htmlFor={this.id}>
{this.props.label}
<input
id={this.id}
value={this.props.value}
onChange={this.onChange}
/>
</label>
);
}
}
复制代码


虽然这里解决 ID 的问题,但是这个方案有漏洞,getNextId()方法每被调用一次,数字会增加,如果是在服务端渲染,这个数字会持续增加到,因此应该在每次渲染之前进行一次重置(每一次网络请求)。因此,一个完整的获取 ID 模块应该是这样:


let count = 1;
export const resetId = () => {
count = 1;
}
export const getNextId = () => {
return element-id-${count++};
}
复制代码

4.通过 props 控制 CSS

当你想在不同的实例中应用不同的 CSS 样式,你可以通过传入不同的 props 值来控制需要应用的样式。表面上看,这样的操作似乎很简单,但实际应用中往往会出现很多错误。我总结共有三种不同的方式来控制组件的样式:


使用主题


借鉴主题的思路,将一系列的 CSS 声明组合在一起,统一成一个主题,在组件中生命组件的主题,例如 primary 按钮以及 secondary 按钮:Hello 一个组件中尽量使用一个主题。


使用标记


也许你的页面中会有一些圆角 button,但这样的风格不符合你已经定义的主题,遇到这种情况,你可能要去找 UI 商量一个统一的方案,或是在元素中添加一个布尔属性,像这样等:<Buttontheme="secondary"rounded>Hello 等同于这种写法:<Button theme=“secondary” rounded{true}>Hello


设置值


当然,你肯定会遇到直接在组建中写 CSS 样式的情况,像这样:<Iconwidth=“25” height=“25” type=“search” />


举个例子


设想你现在需要实现一个链接,但现在有三种截然不同的主题,一些链接有下划线,一些没有,就像这样:



下面给出我的处理方式:


// demo.jsxconst Link = (props) => {
let className = link link--${props.theme}-theme;
if (!props.underline) className += ' link--no-underline';
return <a href={props.href} className={className}>{props.children}</a>;
};
Link.propTypes = {
theme: PropTypes.oneOf([
'default', // primary color, no underline
'blend', // inherit surrounding styles'primary-button', // primary color, solid block
]),
underline: PropTypes.bool,
href: PropTypes.string.isRequired,
children: PropTypes.oneOfType([
PropTypes.element,
PropTypes.array,
PropTypes.string,
]).isRequired,
};
Link.defaultProps = {
theme: 'default',
underline: false,
};
复制代码


CSS 代码如下:


// demo.css
.link--default-theme,
.link--blend-theme:hover {
color: #D84315;
}


.link--blend-theme {
color: inherit;
}
.link--default-theme:hover,
.link--blend-theme:hover {
text-decoration: underline;
}
.link--primary-button-theme {
display: inline-block;
padding: 12px 25px;
font-size: 18px;
background: #D84315;
color: white;
}
.link--no-underline {
text-decoration: none;
}
复制代码


你可能注意到我在类名(例如 link-no-underline )中使用了 --,源自于我过去一直以少写 CSS 代码为目标,但后来意识到这是错的。如果样式可以更好地应用在布局中,我更喜欢使用一些双重和多选择器规则集。虽然我之前提过,但我还想再强调一下,扩展一个网站最困难的部分是 CSS 的部分,Javascript 部分都很容易,CSS 开始就写的很混乱,后面维护会非常困难,一入布局深似海。实际项目中,一些 web 开发者往往被 CSS 特异性给难倒了,如果你在浏览网页,不妨查看一下页面中的元素(比如导航栏中的提示图标)是如何用 CSS 实现的。如果你不想打开控制台去找,也可以思考这个元素(比如圆圈中包含数字)的实现样式涉及了哪些 CSS 规


则。


译者注:所说的元素包含在原文站点中


共有二十三条规则,还不包括从其他十一条规则下集成的规则,其中 line-height


被复写了九次……即便 line-height 是一只猫,也未能幸免于难。



译者注:猫有九命的梗


使用 React 之后,我们更好地处理页面样式,更周到地决定设计哪些类应用在我们的组件中,将全局设置迁移到 Button.scss 文件中,移除所有对于特异性以及文件顺序的依赖。边注:我梦想有一天,我们再也不需要浏览器对于样式使用的建议。::user-agent-styles: none-whatsoever; 让这样的梦想成为现实。

5.开关组件

开关组件是呈现众多组件之一的组件。可以是用来展示页面的。组件或者是 tab 集合中的 tab,也可以是模态组件中的不同模式。


我过去习惯使用 switch 语句处理,实际传递到我想要渲染的组件,再从组件本身导出对组件的引用(作为命名导出,作为组件的属性)。


现在看来这些都是可怕的方式,我已经解决的一个潜在危险方法是我用一个对象将 prop 值映射到组件中。


import HomePage from './HomePage.jsx';
import AboutPage from './AboutPage.jsx';
import UserPage from './UserPage.jsx';
import FourOhFourPage from './FourOhFourPage.jsx';
const PAGES = {
home: HomePage,
about: AboutPage,
user: UserPage,
};
const Page = (props) => {
const Handler = PAGES[props.page] || FourOhFourPage;
return <Handler {...props} />
};
Page.propTypes = {
page: PropTypes.oneOf(Object.keys(PAGES)).isRequired,
};
复制代码


PAGE 对象中的值可以在 prop 类型中用来捕获开发时错误。当然,我们可以像这样使用 ,如果你将 home、about 缓和 user ,分别提换成/、/about 以及 /user,那么你就有了半个路由啦。(下一步想法:移除 react-router)

6.进入组件内部件

如果你想提高用户体验,不妨试试在页面输入较频繁的输入框添加 autofocus,非常简单,但却可以大大提高用户的使用体验。设想页面中有一个登陆表单,而作为“用户体验高级设计师”的你想在表单的“用户名”输入框中添加一个闪烁的光标,但发现登陆表单显示在模态框中,而 autofocus 属性只能应用在页面加载。


现在你该怎么办 ?


你可能会用 Javascript 实现,给 input 标签一个 id,再用


document.getElementById(‘user-name-input’).focus()让输入框聚焦。这种方法虽然有效,但不够优雅,你的程序中对字符串匹配的依赖应该越少越好。比较幸运的是,有一种非常简单的方法可以实现这个效果:


class SignInModal extends Component {
componentDidMount() {
this.InputComponent.focus();
}
render() {
return (
<div>
<label>User name: </label>
<Input
ref={comp => { this.InputComponent = comp; }}
/>
</div>
)
}
}
复制代码


需要注意的是,当你对组件使用 ref 时,是对组件的引用(而不是底层元素),所以你可以访问其方法。

7.almost component

如设想你正在写一个搜索用户的组件,当你输入的时候,你会看到一列潜在匹配的用户名和头像,就像这样。



当你在设计这个组件时,你可能会犹豫,列表中的每一项都属 SearchSuggestion 组件吗 ?只有几行 HTML 和 CSS 代码,也许不是 ?但我曾经告诉自己:“如果感到疑惑,那就再建一个新组件”。我如果这样做,就一个单独的组件都没了。相反,只有一个给每个入口返回对应 DOM 的 renderSearchSuggestion 方法,我就生成了如下的结果:


const SearchSuggestions = (props) => {
// renderSearchSuggestion() behaves as a pseduo SearchSuggestion component
// keep it self contained and it should be easy to extract later if needed
const renderSearchSuggestion = listItem => (
<li key={listItem.id}>{listItem.name} {listItem.id}</li>
);
return (
<ul>
{props.listItems.map(renderSearchSuggestion)}
</ul>
);
}
复制代码


如果需求变得更复杂或者你想在其他地方使用这个组件,你可以把这段代码复制到新的组件中。


不要过早地组件化,组件不像茶匙,你可以有很多组件。


我的意思不是要你把你觉得应该独立成组件的部分合并到父组件中,而是想让你把那些你认为不应该独立成组件的部分做一些改进,让它看起来和所在的组件更贴合(如果可以的话)。

8.用于格式化文字的组件

当我刚接触 React 时,我觉得组件是一个非常大的东西,一种给 DOM 结构分组的方法,但实际上,组件就像是用于格式化的一种方法。这里有一个 组件,输入一个数字会返回一个漂亮的字符串(加上小数点或者 $ 符)。


constPrice = (props) => {
constprice = props.children.toLocaleString('en', {
style: props.showSymbol ? 'currency' :undefined,
currency: props.showSymbol ? 'USD' :undefined,
maximumFractionDigits: props.showDecimals ? 2: 0,
});
return<span className={props.className}>{price}</span>
};

Price.propTypes= {
className: React.PropTypes.string,
children: React.PropTypes.number,
showDecimals: React.PropTypes.bool,
showSymbol: React.PropTypes.bool,
};

Price.defaultProps= {
children: 0,
showDecimals: true,
showSymbol: true,
};

constPage = () => {
const lambPrice = 1234.567;
const jetPrice = 999999.99;
const bootPrice = 34.567;
return (
<div>
<p>One lamb is <PriceclassName="expensive">{lambPrice}</Price></p>
<p>One jet is <PriceshowDecimals={false}>{jetPrice}</Price></p>
<p>Those gumboots will set ya back<Price showDecimals={false} showSymbol={false}>{bootPrice}</Price>bucks.</p>
</div>
);
};
复制代码


注意:代码中没有对获取的数字进行校验……

9.Store 服务于组件

这行代码我已经写过无数遍了(虽然夸张了点):if (props.user.signInStatus === SIGN_IN_STATUSES.SIGNED_IN)…最近我意识到,我这样做是不是错了,我想知道的是“用户登录了没”,而不是“用户登录的状态是否等于已登录 ?”对于我的组件而言,他们应该有足够的发展,而不该因为了忧虑这些小事叨扰它们,他们不该管得到的 price 参数是否是 Number 类型,也不应该为了一个参数 true 或者 false 烦心。如你所见,如果在 store 中定义的数据符合你的组件要求,你的组件就会简洁很多。如我之前所说,bug 隐藏在复杂逻辑之后,你的组件越简洁,出现 bug 的几率就越低。但开发中肯定会遇到一些复杂的场景,关于如何解决这些问题,我这里有几点经验:


  1. 制定组件的一般结构以及其所需要的数据

  2. 设计满足这些需求的 stroe

  3. 尽量使你传入的数据匹配 stroe 的要求关于最后一点,我建议创建一个单独的模块来完成所有输入数据的格式处理,属性重命名、字符串转数字、对象转数组、Date 字符串转 Date 对象等等。


10.不要通过相对路径引入组件


import Button from ‘…/…/…/…/Button/Button.jsx’;


import Icon from ‘…/…/…/…/Icon/Icon.jsx’;


import Footer from ‘…/…/Footer/Footer.jsx’;


用下面的方式替代上面的引用方式,是不是觉得清爽很多 ? import {Button, Icon, Footer} from ‘Components’;理论上可以这么做


  • 创建一个 index.js 文件来引用你所有的组件

  • 使用 Webpack 的 resolve.alias 来重定向所有组件到 index 文件我目前还没尝试过这种方法,我打算在先有的项目中拿一个出来转换成这样的组织方式(蛤蛤,骗你的,我一直都是这么做的)。但正如我之前写的代码一样,我后来意识到这种方式是错的,原因如下:


1.Webpack 2 中的 resolve.alias 失效了


2.因为组件不在 node_modules 里,所以这算是一个 eslint 错误


3.如果你有一个好的 IDE,那么它会知道项目里的所有组件,如果你忘了加一些属性值,它会温馨地提示你添加,你可以通过 cmd/Ctrl + 点击就可以打开这些组件所在的文件。如果用我之前的方式引用组件,那么 IDE 将找不到我的组件的位置,我就失去了这些温馨智能的功能。



标注:matthew hsiung 在关于 eslint 和 WebStorm 的 issue 回复下面提供了一


个解决方案


本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。


原文链接:


https://mp.weixin.qq.com/s/bEAL19teUdROOaZ4_un0UA


2019-09-20 16:11754

评论

发布
暂无评论
发现更多内容

DR Auto-Sync 搭建和计划内切换操作手册

TiDB 社区干货传送门

dm-V1.0.5使用汇总

TiDB 社区干货传送门

管理与运维

PointGet的一生

TiDB 社区干货传送门

开发语言 TiDB 底层架构 TiDB 源码解读 TiKV 源码解读 TiKV 底层架构

数据库调优之硬件

TiDB 社区干货传送门

性能调优

TIKV、PD添加TLS总结

TiDB 社区干货传送门

实践案例

TiDB与众不同的优化器

TiDB 社区干货传送门

性能测评 应用适配

从单点到分布式的哲学启蒙

TiDB 社区干货传送门

数据库前沿趋势

【白皮书】TiDelta,一款简单易用的 TiDB 性能对比 Web 工具。

TiDB 社区干货传送门

悲观锁模式下 TiDB 与其他数据库在 RC 及 RR 隔离级别的行为对比

TiDB 社区干货传送门

TPC-H 下 TiFlash 的扩展性测试报告 - v5.1.0

TiDB 社区干货传送门

版本测评 性能测评

【备考指南】新版 PingCAP PCTP 认证考试

TiDB 社区干货传送门

TiDB源码系列之沉浸式编译TiDB

TiDB 社区干货传送门

TiDB 源码解读

Raft/Paxos类协议与分布式事务

TiDB 社区干货传送门

数据库架构设计

TiDB 5.4 单机快速安装初体验

TiDB 社区干货传送门

管理与运维 版本测评

TiEM初体验

TiDB 社区干货传送门

集群管理 管理与运维 6.x 实践

ticdc没报错,tso却不变的奇怪现象

TiDB 社区干货传送门

TiSpark 2.4.1(Spark 2.4.5)到TiSpark 2.5.0(Spark 3.0.X/3.1.X)迁移实践

TiDB 社区干货传送门

实践案例

DM 同步 modify column 语句到 TiDB 5.3 踩坑二:DDL 语句重放

TiDB 社区干货传送门

实践案例 故障排查/诊断

TiDB热点测试

TiDB 社区干货传送门

版本测评 性能测评

文盘Rust -- 生命周期问题引发的 static hashmap 锁

TiDB 社区干货传送门

开发语言

黄东旭: 关于基础软件产品价值的思考

TiDB 社区干货传送门

在CentOS7上进行TiDB/PD/TIKV编译分享

TiDB 社区干货传送门

实践案例 安装 & 部署

用 Gravity 实现 MongoDB 到 TiDB 的数据复制

TiDB 社区干货传送门

TiDB统计信息原理简介与实践

TiDB 社区干货传送门

管理与运维

Tidb为什么能做到国产第一

TiDB 社区干货传送门

性能测评 数据库架构设计 应用适配

混沌工程在建信金科的应用实践

TiDB 社区干货传送门

实践案例 故障排查/诊断 TUG 话题探讨

TiDB中快速恢复被Truncate 的表

TiDB 社区干货传送门

管理与运维

TiDB 5.4 发版丨新功能解读

TiDB 社区干货传送门

DM 同步 modify column 语句到 TiDB 5.3 踩坑一:数据乱码

TiDB 社区干货传送门

实践案例 故障排查/诊断

温故知新 | mydumper & dumpling 知识点汇总

TiDB 社区干货传送门

【专栏平台上线】来自社区的一份感谢信,致谢 137 位 TiDB 社区技术布道师

TiDB 社区干货传送门

React 的 10 种迷你开发模式_文化 & 方法_李俊冬_InfoQ精选文章