【ArchSummit】如何通过AIOps推动可量化的业务价值增长和效率提升?>>> 了解详情
写点什么

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:11756

评论

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

PoseiSwap 即将在 6 月 18 日开启 IDO,白名单申请者超 14000 人

EOSdreamer111

语音社交源码知识语音房间功能的实现

山东布谷科技

源码 软件开发 源码搭建 语音社交软件 语音厅平台搭建

PoseiSwap 即将在 6 月 18 日开启 IDO,白名单申请者超 14000 人

西柚子

PoseiSwap 即将在 6 月 18 日开启 IDO,白名单申请者超 14000 人

股市老人

革新DeFi:Hover赋能Cosmosphere的金融自由

股市老人

Nestjs+Vue实现阿里云OSS服务端签名直传

昆吾kw

Vue OSS 文件上传 nest

大牛一次带你彻底搞懂Spring核心容器的动态代理机制与SpringAOP

互联网架构师小马

某大厂工作3年,被劣驱良了。。。

Jackpop

STM32配合W5500网卡连接MQTT服务器

DS小龙哥

6 月 优质更文活动

软件开发人员必须阅读的20本书

越长大越悲伤

架构 微服务 软件开发

Spring核心容器:AOP代理机制对性能的影响案例分析

互联网架构师小马

深入理解Spring核心容器面向切面概念和实践:AOP实战经验

互联网架构师小马

还没毕业,就“被毕业”,太惨了!

Jackpop

PoseiSwap 即将在 6 月 18 日开启 IDO,白名单申请者超 14000 人

BlockChain先知

PoseiSwap 即将在 6 月 18 日开启 IDO,白名单申请者超 14000 人

大瞿科技

难得可贵的Spring依赖注入实战经验,是程序员就该吸收一下!

互联网架构师小马

Java spring

一次性搞懂springweb服务构建轻量级Web技术体系:Spring WebMVC

互联网架构师小马

Controller Manager原理分析

穿过生命散发芬芳

6 月 优质更文活动

Generative AI 新世界:过去、现在和未来

亚马逊云科技 (Amazon Web Services)

人工智能 机器学习

C语言编程语法—语法风格

智趣匠

C语言 语法风格 6 月 优质更文活动

入职新公司,水土不服该怎么破?

Jackpop

3年经验程序员终于把Spring核心容器的面向切面与Spring AOP搞懂了

互联网架构师小马

PoseiSwap 即将在 6 月 18 日开启 IDO,白名单申请者超 14000 人

威廉META

Easysearch 跨版本兼容性测试,还原 Elasticsearch 各版本快照数据

极限实验室

搜索引擎 elasticsearch INFINI Labs easysearch 快照恢复

PoseiSwap 即将在 6 月 18 日开启 IDO,白名单申请者超 14000 人

鳄鱼视界

Kubernetes Scheduler原理分析

穿过生命散发芬芳

k8s 6 月 优质更文活动

MegEngine 动态执行引擎 Imperative Runtime 架构解析

MegEngineBot

深度学习 开源框架 MegEngine

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