Open EthanLin-TWer opened 7 years ago
简单来说,组件中经常会出现 if-else
这样的渲染逻辑,比如列表长度为0时不渲染组件,比如在一个大组件中,选择性决定是否渲染某个小部分。有了 if-else
,就一定要出现 JSX 的 { }
标签,原本顺畅的声明式 UI JSX 语法就难看许多。因此,如前所述,为了保证 React 声明式 UI 代码的编写和阅读能力,我们的原则是,使用尽量少的 JSX 语法来处理组件中的 if
渲染逻辑。
经过考察各种各样的方案,只有下面👇这种方式是我所能勉强接受的、差强人意的解决方案。已经很难看了。
const NewsItem = ({ text, optionalVideoId }) => (
<Text>{ text }</Text>
{ !!optionalVideoId && (
<VideoPlayer id={optionalVideoId} />
) }
)
下面👇这种很清晰,多出来的 if
JSX 非但不影响效果,反而让组件「读」起来也很顺畅。从阅读上讲比上面👆的写法好不少,但上面其实是相对比较复杂的 case,无法用这种手法进行处理。
const Login = ({ isLoggedIn, loginMode }) => {
if (isLoggedIn) {
return <Profile loginMode={ loginMode } />
}
return <Login />
}
const Login = ({ isLoggedIn, loginMode }) => {
return isLoggedIn ? <Profile loginMode={ loginMode } /> : <Login />
}
对于列表渲染,RN 中有 FlatList
SectionList
这样内置的类用以渲染列表,但 react 中一般还是需要通过手动的 map
来处理。手动 map 的缺点是太过命令式,需要写一些样板代码,并且最重要的是让 React 的声明式 UI 能力和阅读体验大打折扣。然后我觉得,下面这些常见的逻辑,其实都可以通过封装组件来达到声明式的目的。
<If condition={data.length !== 0}>
<Data />
</If>
<List data={data} renderItem={({ id, title }) => <div>{id}: {title}</div>} />
卧槽,今天发现这些库干的事情就跟我想的一样啊,用来对付简单逻辑,又获得模板声明式的好处,又可以省去测试并依然有信心。然后复杂逻辑就得益于 Vanilla JS 来处理,简直 React 完美的解决方案:
首先性能优化界有句经典的名言,叫「Measure/Profile first, optimise second」,意思就是先分析瓶颈,再进行优化。这是推崇数据先行的思路,理由是我们从理论上推导所知的性能瓶颈,不一定是真正的问题所在,因为在我们写的代码和实际在机器上运行的代码中间,还可能有编译器或框架本身会为我们做优化,这中间过程是不可见不可预测的。因此,先做 profile,从数据上看到性能瓶颈以后再进行优化,往往是比较高效的做法。
然后,程墨在 《浅出》 中引用了 Donald 的名言:「过早优化是万恶之源」并补全了原句,指出其实过早优化非架构上的、对性能影响不大的代码才是万恶之源,对核心代码的架构和性能进行优化是合理的,并且应该时时进行,越早越好,越到后面成本越高。至于什么算核心代码,大概没人能给出明确定义,只能依靠各人经验而为了。但他这里就指出了,经验(而非数据)在优化领域还是有用武之地的。
那么,以 React 为 UI 库的代码还有什么可优化的地方?不是说 React 的 diff 算法已经非常牛逼,reconciliation 已经非常高效了么?这是因为,React 只是个库(愿意的话也可叫框架),而没有任何库能完美契合 所有 使用者的需求。这就导致,为了保证绝大多数人的正常使用,它必须使用保守的设计策略,而不能假设每个开发者都具有比较丰富的背景知识。这个正确而保守的策略就为高级开发者带来了优化的空间。那么问题来了,你,是不是(是否愿意成为)这个高级的开发者呢?
React 组件的优化策略从大面上分列如下:
render
shouldComponentUpdate
key
属性reselect
render
shouldComponentUpdate
这个方法是组件渲染前会被 React 询问的方法,如果返回 false
,则组件不会被重新渲染。能否精准地控制这个方法的返回,是提升渲染、更新性能的重要因素。那么同学可能会有疑问,我写了几个月的前端代码,我们项目也没几个地方出现 shouldComponentUpdate
方法啊,难道这就说明我们项目的代码性能都不行吗?嗯,说对了一半。
待补。
key
属性reselect
经过一段时间使用发现,reselect 简直是 redux 项目的标配。如果你的 mapStateToProps()
中都仅是非常简单的从 store 中挑值,那当我没说,你很大概率不需要 reselect。但假如有以下的场景,那么几乎可以肯定 reselect 会是一个很好的方案:
mapStateToProps
中存在数据计算,你需要缓存以提升性能
mapStateToProps
中存在数据结构变换,通常因为前端 view 需要的数据不是 redux 原始的数据结构
mapStateToProps
封装一层,以达到 隔离组件与数据结构(redux 数据结构变化仅影响 reducer 和 selector) 或 建立统一数据获取逻辑(同一段数据获取逻辑可以被多个组件复用) 等目的render
中计算差异 - 这样每次可能都会重新计算。然后又需要在 render
中引入 memoize
用上 reselect 以后你可能会发现另一个问题,就是当你一份同样的 reducer 逻辑需要给多个组件使用时,你需要一个机制来区分。很多时候这个机制就是 const mapStateToProps = (store, ownProps) => ({})
。好,这个机制可能会有问题,因为 ownProps
可能会一直变,导致 selector 多次重复计算,失去其缓存的优势。
问题的本质在于,ownProps
共享的不只是 reducer 核心逻辑,还需要有各自独立的数据结构,而 reselect
不能提供各自独立的缓存层,它只有一层。所以不仅需要一套机制共享 reducer 逻辑,还需要另一套机制,提供互相隔绝的数据缓存层。解决方案:
mapStateToProps
,这也是 react-redux 提供了专门支持的re-reselect
非常期待 reselect
这一部分。
React.createElement()
API
如何学习一样新东西?
我希望以前端作为职业切入点来立足于行业(而事实上我现在的工作更多跟NodeJS与后端有关系……),而React又是前端中我选择的切入点,所以这个系列希望能以系统化的思路来组织React、产出学习经验,也即是最好有一个学习路线图。
2017年学习的时候,React官方对自己的定位是一个声明式、函数式的UI库。其处理的核心是UI,优势在于其声明式、组件化的特性提高了前端代码的可维护性和可扩展性。而前端工作相关的数据仓库、副作用、路由这三个主要部分,主要是由周边的组件/库来负责。
在社区多个解决方案的迭代中,逐渐形成了以
react-redux
/redux-saga
/react-router
为主要解决方案的几个组件(当然还有其他的方案,比如mobx
/redux-thunk
/react-router
v3和v4的不同路由API等),也出现了以封装最佳实践、简化模板代码为初衷的二次框架,如dva
/umi
系列。UI方面,antd
也为许多企业应用所接纳。而到了2020年的今天,React在UI这个核心以外在这三大方面中的数据流和副作用方面又多做了一些工作(比如
Context
API、hooks
API等),使得以“前端问题域”为核心的解决方案更加完善,当然是赋能了开发者能用React来更简单地做前端的解决方案。这一点,在阅读《深入浅出React&Redux》的时候如能对比思考,会有更多收获。《深入浅出》建构的路线图 与 读书法
如果把“前端解决方案”作为一个整体来看待,在React(当然其他的技术栈也成立)这个套餐内它的各个部分分别是:React本身提供UI能力、数据流/副作用/路由由社区方案负责。这本书的结构也围绕这几个点展开:
React部分的章节:
props/state
、生命周期函数社区方案的部分:
redux-thunk
、redux-promise
等react-router
以及相关主题等第8章“单元测试”写得没我好,三观也不正,属于和稀泥、为了讨好更多受众的写法:“测也好不测也好,对开发有用才是好”。说得好像没测试还会更好似的。但问题的关键也不在这,关键是你不练怎么会好呢?你不练TDD,怎么知道什么才是对开发有用的测试呢?第10章、第12章可选读,没事不用读。
除此以外,一个正常的前端项目还必须有以下的工具/能力支撑,好在到了2020年的今天,许多方案选型已经相对固定了:
目录
props
,state
, andPropTypes
state
的缺陷context
React.createElement()
API最佳的学习资料