Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1. 纯 React 技术目录 #122

Open
20 of 42 tasks
EthanLin-TWer opened this issue Feb 25, 2017 · 7 comments
Open
20 of 42 tasks

1. 纯 React 技术目录 #122

EthanLin-TWer opened this issue Feb 25, 2017 · 7 comments
Assignees

Comments

@EthanLin-TWer
Copy link
Owner

EthanLin-TWer commented Feb 25, 2017

如何学习一样新东西?

  • 是什么,为什么(解决了什么问题、或是更好地解决了某个问题),有啥竞品,如何使用
  • 穿插学习、动手实践、社团、测试自己

我希望以前端作为职业切入点来立足于行业(而事实上我现在的工作更多跟NodeJS与后端有关系……),而React又是前端中我选择的切入点,所以这个系列希望能以系统化的思路来组织React、产出学习经验,也即是最好有一个学习路线图。

2017年学习的时候,React官方对自己的定位是一个声明式、函数式的UI库。其处理的核心是UI,优势在于其声明式、组件化的特性提高了前端代码的可维护性和可扩展性。而前端工作相关的数据仓库、副作用、路由这三个主要部分,主要是由周边的组件/库来负责。

在社区多个解决方案的迭代中,逐渐形成了以react-redux/redux-saga/react-router为主要解决方案的几个组件(当然还有其他的方案,比如mobx/redux-thunk/react-routerv3和v4的不同路由API等),也出现了以封装最佳实践、简化模板代码为初衷的二次框架,如dva/umi系列。UI方面,antd也为许多企业应用所接纳。

而到了2020年的今天,React在UI这个核心以外在这三大方面中的数据流和副作用方面又多做了一些工作(比如Context API、hooks API等),使得以“前端问题域”为核心的解决方案更加完善,当然是赋能了开发者能用React来更简单地做前端的解决方案。这一点,在阅读《深入浅出React&Redux》的时候如能对比思考,会有更多收获。

《深入浅出》建构的路线图 与 读书法

如果把“前端解决方案”作为一个整体来看待,在React(当然其他的技术栈也成立)这个套餐内它的各个部分分别是:React本身提供UI能力、数据流/副作用/路由由社区方案负责。这本书的结构也围绕这几个点展开:

React部分的章节:

  • 第1章。提纲挈领就一句话:“React就是个支持声明式、组件化的UI框架”,指明了React能力范围
  • 第2章。提到React支撑前端UI的基本能力:基本数据模型props/state、生命周期函数
  • 第5章。性能优化:高级用户必读
  • 第6章。高阶组件:高级用户必读

社区方案的部分:

  • 第3章。数据管理:Flux思路、Redux入门
  • 第4章。模块化:讲述React的“模块化”结合redux如何实现,有何要点,宜精读
  • 第7章。副作用:讲述了当是时的社区方案,如redux-thunkredux-promise
  • 第9章。中间件:redux特性,高级用户必读
  • 第11章。路由:讲述了当是时的社区方案:react-router以及相关主题等

第8章“单元测试”写得没我好,三观也不正,属于和稀泥、为了讨好更多受众的写法:“测也好不测也好,对开发有用才是好”。说得好像没测试还会更好似的。但问题的关键也不在这,关键是你不练怎么会好呢?你不练TDD,怎么知道什么才是对开发有用的测试呢?第10章、第12章可选读,没事不用读。

除此以外,一个正常的前端项目还必须有以下的工具/能力支撑,好在到了2020年的今天,许多方案选型已经相对固定了:

  • 周边生态:seamless-immutable & reselect
  • 类型系统:TypeScript
  • 构建工具:webpack
  • 测试工具:jest & enzyme
  • 包管理:npm / yarn
  • 风格检查:ESLint(TSLint) / prettier

目录

  • 0. 前置知识: ES6 & JSX - 不在本篇覆盖,见 专注的 React 技术栈成长路线 #184 目录
  • 1. React 精华 - 声明式、组件化
  • 2. React 基础
    • React with ES6
    • React with JSX
    • React elements
    • React components
    • Event handling
  • 3. React 数据流
    • props, state, and PropTypes
    • 组件通信 - state 的缺陷
    • 生命周期方法
    • 轻量级数据交换方案:context
  • 4. 高阶组件
  • 5. 性能优化
  • 6. 工程实践
    • 组件中的条件逻辑与列表渲染 - Conditional Rendering & List Rendering
    • 组件拆分的粒度
    • 项目目录结构划分
  • 7. 常用重构手法和刻意练习
  • 8. 单元测试
  • 9. 深入原理 - React 核心机制
    • React.createElement() API
    • Virtual DOM 实现
    • Fiber / Reconciliation / diff 算法
  • 10. SSR - 服务器端渲染
  • 11. 生态圈 - 不在本篇覆盖,见 专注的 React 技术栈成长路线 #184 目录

最佳的学习资料

@EthanLin-TWer EthanLin-TWer added this to the Iteration 2: Cookie🍪 milestone Feb 25, 2017
@EthanLin-TWer EthanLin-TWer self-assigned this Feb 25, 2017
@EthanLin-TWer EthanLin-TWer removed this from the Iteration 2: Cookie🍪 milestone Mar 1, 2017
@EthanLin-TWer EthanLin-TWer changed the title React Foundamentals 1. React Foundamentals and ES6 Mar 14, 2017
@EthanLin-TWer EthanLin-TWer removed this from the Iteration 4: Husky🐶 milestone Jul 29, 2017
@EthanLin-TWer
Copy link
Owner Author

EthanLin-TWer commented Oct 26, 2017

常用重构手法和刻意练习

  • 将 functional component 改成 class component
  • 将 class component 改成 functional component
  • 写一套 redux 设施(简化框架代码、刻意练习步骤、小步提交)
  • 重构一个 reducer 的字段名

@EthanLin-TWer
Copy link
Owner Author

EthanLin-TWer commented Oct 28, 2017

工程实践

组件中的条件逻辑与列表渲染 - Conditional Rendering & List Data Rendering

简单来说,组件中经常会出现 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 条件满足时渲染
  • 渲染列表
  • 字符串不为空时渲染
<If condition={data.length !== 0}>
  <Data />
</If>
<List data={data} renderItem={({ id, title }) => <div>{id}: {title}</div>} />

卧槽,今天发现这些库干的事情就跟我想的一样啊,用来对付简单逻辑,又获得模板声明式的好处,又可以省去测试并依然有信心。然后复杂逻辑就得益于 Vanilla JS 来处理,简直 React 完美的解决方案:

组件的拆分粒度

项目目录结构划分

  • functional(actions/reducers/selectors/components/containers)
  • duck

@EthanLin-TWer EthanLin-TWer changed the title 1. React Foundamentals and ES6 1. 纯 React 技术目录 Oct 31, 2017
@EthanLin-TWer
Copy link
Owner Author

EthanLin-TWer commented Oct 31, 2017

单元测试

#200

@EthanLin-TWer
Copy link
Owner Author

EthanLin-TWer commented Nov 1, 2017

性能优化

首先性能优化界有句经典的名言,叫「Measure/Profile first, optimise second」,意思就是先分析瓶颈,再进行优化。这是推崇数据先行的思路,理由是我们从理论上推导所知的性能瓶颈,不一定是真正的问题所在,因为在我们写的代码和实际在机器上运行的代码中间,还可能有编译器或框架本身会为我们做优化,这中间过程是不可见不可预测的。因此,先做 profile,从数据上看到性能瓶颈以后再进行优化,往往是比较高效的做法。

然后,程墨在 《浅出》 中引用了 Donald 的名言:「过早优化是万恶之源」并补全了原句,指出其实过早优化非架构上的、对性能影响不大的代码才是万恶之源,对核心代码的架构和性能进行优化是合理的,并且应该时时进行,越早越好,越到后面成本越高。至于什么算核心代码,大概没人能给出明确定义,只能依靠各人经验而为了。但他这里就指出了,经验(而非数据)在优化领域还是有用武之地的。

那么,以 React 为 UI 库的代码还有什么可优化的地方?不是说 React 的 diff 算法已经非常牛逼,reconciliation 已经非常高效了么?这是因为,React 只是个库(愿意的话也可叫框架),而没有任何库能完美契合 所有 使用者的需求。这就导致,为了保证绝大多数人的正常使用,它必须使用保守的设计策略,而不能假设每个开发者都具有比较丰富的背景知识。这个正确而保守的策略就为高级开发者带来了优化的空间。那么问题来了,你,是不是(是否愿意成为)这个高级的开发者呢?

React 组件的优化策略从大面上分列如下:

  • 避免不必要的 re-render
    • 定制 shouldComponentUpdate
    • 数组唯一的 key 属性
  • 避免不必要的数据计算
    • 纯净的副作用方案:reselect

避免不必要的 re-render

shouldComponentUpdate

这个方法是组件渲染前会被 React 询问的方法,如果返回 false,则组件不会被重新渲染。能否精准地控制这个方法的返回,是提升渲染、更新性能的重要因素。那么同学可能会有疑问,我写了几个月的前端代码,我们项目也没几个地方出现 shouldComponentUpdate 方法啊,难道这就说明我们项目的代码性能都不行吗?嗯,说对了一半。

待补。

数组唯一的 key 属性

避免不必要的数据计算

使用 reselect

经过一段时间使用发现,reselect 简直是 redux 项目的标配。如果你的 mapStateToProps() 中都仅是非常简单的从 store 中挑值,那当我没说,你很大概率不需要 reselect。但假如有以下的场景,那么几乎可以肯定 reselect 会是一个很好的方案:

  • mapStateToProps 中存在数据计算,你需要缓存以提升性能
    • 增删数据结构
    • 从已知数据计算状态
  • mapStateToProps 中存在数据结构变换,通常因为前端 view 需要的数据不是 redux 原始的数据结构
    • 实战场景1:章节分页
    • 实战场景2:建立索引
  • 需要将 mapStateToProps 封装一层,以达到 隔离组件与数据结构(redux 数据结构变化仅影响 reducer 和 selector)建立统一数据获取逻辑(同一段数据获取逻辑可以被多个组件复用) 等目的

其他方案

  • render 中计算差异 - 这样每次可能都会重新计算。然后又需要在 render 中引入 memoize
  • 将关联计算放在 reducer 并将算完的数据放到 redux - 这样 redux state 树会出现数据冗余。数据冗余有什么不好呢?维护一致性有成本和风险、可能不想级联修改

其他问题

用上 reselect 以后你可能会发现另一个问题,就是当你一份同样的 reducer 逻辑需要给多个组件使用时,你需要一个机制来区分。很多时候这个机制就是 const mapStateToProps = (store, ownProps) => ({})。好,这个机制可能会有问题,因为 ownProps 可能会一直变,导致 selector 多次重复计算,失去其缓存的优势。

问题的本质在于,ownProps 共享的不只是 reducer 核心逻辑,还需要有各自独立的数据结构,而 reselect 不能提供各自独立的缓存层,它只有一层。所以不仅需要一套机制共享 reducer 逻辑,还需要另一套机制,提供互相隔绝的数据缓存层。解决方案:

参考资料

@JimmyLv
Copy link

JimmyLv commented Nov 1, 2017

非常期待 reselect 这一部分。

@EthanLin-TWer
Copy link
Owner Author

深入原理 - React 核心机制

React.createElement() API

Virtual DOM 实现

Fiber / Reconciliation / diff 算法

SSR - 服务器端渲染

@EthanLin-TWer
Copy link
Owner Author

高阶组件

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants