过去几年里,我参与了几个较大的 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 组件中使用,如下所示:
虽然这里解决 ID 的问题,但是这个方案有漏洞,getNextId()方法每被调用一次,数字会增加,如果是在服务端渲染,这个数字会持续增加到,因此应该在每次渲染之前进行一次重置(每一次网络请求)。因此,一个完整的获取 ID 模块应该是这样:
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” />
举个例子
设想你现在需要实现一个链接,但现在有三种截然不同的主题,一些链接有下划线,一些没有,就像这样:
下面给出我的处理方式:
CSS 代码如下:
你可能注意到我在类名(例如 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 值映射到组件中。
PAGE 对象中的值可以在 prop 类型中用来捕获开发时错误。当然,我们可以像这样使用 ,如果你将 home、about 缓和 user ,分别提换成/、/about 以及 /user,那么你就有了半个路由啦。(下一步想法:移除 react-router)
6.进入组件内部件
如果你想提高用户体验,不妨试试在页面输入较频繁的输入框添加 autofocus,非常简单,但却可以大大提高用户的使用体验。设想页面中有一个登陆表单,而作为“用户体验高级设计师”的你想在表单的“用户名”输入框中添加一个闪烁的光标,但发现登陆表单显示在模态框中,而 autofocus 属性只能应用在页面加载。
现在你该怎么办 ?
你可能会用 Javascript 实现,给 input 标签一个 id,再用
document.getElementById(‘user-name-input’).focus()让输入框聚焦。这种方法虽然有效,但不够优雅,你的程序中对字符串匹配的依赖应该越少越好。比较幸运的是,有一种非常简单的方法可以实现这个效果:
需要注意的是,当你对组件使用 ref 时,是对组件的引用(而不是底层元素),所以你可以访问其方法。
7.almost component
如设想你正在写一个搜索用户的组件,当你输入的时候,你会看到一列潜在匹配的用户名和头像,就像这样。
当你在设计这个组件时,你可能会犹豫,列表中的每一项都属 SearchSuggestion 组件吗 ?只有几行 HTML 和 CSS 代码,也许不是 ?但我曾经告诉自己:“如果感到疑惑,那就再建一个新组件”。我如果这样做,就一个单独的组件都没了。相反,只有一个给每个入口返回对应 DOM 的 renderSearchSuggestion 方法,我就生成了如下的结果:
如果需求变得更复杂或者你想在其他地方使用这个组件,你可以把这段代码复制到新的组件中。
不要过早地组件化,组件不像茶匙,你可以有很多组件。
我的意思不是要你把你觉得应该独立成组件的部分合并到父组件中,而是想让你把那些你认为不应该独立成组件的部分做一些改进,让它看起来和所在的组件更贴合(如果可以的话)。
8.用于格式化文字的组件
当我刚接触 React 时,我觉得组件是一个非常大的东西,一种给 DOM 结构分组的方法,但实际上,组件就像是用于格式化的一种方法。这里有一个 组件,输入一个数字会返回一个漂亮的字符串(加上小数点或者 $ 符)。
注意:代码中没有对获取的数字进行校验……
9.Store 服务于组件
这行代码我已经写过无数遍了(虽然夸张了点):if (props.user.signInStatus === SIGN_IN_STATUSES.SIGNED_IN)…最近我意识到,我这样做是不是错了,我想知道的是“用户登录了没”,而不是“用户登录的状态是否等于已登录 ?”对于我的组件而言,他们应该有足够的发展,而不该因为了忧虑这些小事叨扰它们,他们不该管得到的 price 参数是否是 Number 类型,也不应该为了一个参数 true 或者 false 烦心。如你所见,如果在 store 中定义的数据符合你的组件要求,你的组件就会简洁很多。如我之前所说,bug 隐藏在复杂逻辑之后,你的组件越简洁,出现 bug 的几率就越低。但开发中肯定会遇到一些复杂的场景,关于如何解决这些问题,我这里有几点经验:
制定组件的一般结构以及其所需要的数据
设计满足这些需求的 stroe
尽量使你传入的数据匹配 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
评论