0%

引入的模块

1
2
3
4
var ReactElement = require("ReactElement");

var emptyFunction = require("fbjs/lib/emptyFunction");
var invariant = require("fbjs/lib/invariant");

我们来看一下 ReactElement 模块,其他两个都是工具函数,不用关心。

Export 的对象

1
2
3
4
5
6
7
8
var ReactChildren = {
forEach: forEachChildren,
map: mapChildren,
count: countChildren,
toArray: toArray,
};

module.exports = ReactChildren;

依次来看一下这个四个 API

forEach

1
2
3
4
5
6
7
8
9
10
11
12
13
function forEachChildren(children, forEachFunc, forEachContext) {
if (children == null) {
return children;
}
var traverseContext = getPooledTraverseContext(
null,
null,
forEachFunc,
forEachContext
);
traverseAllChildren(children, forEachSingleChild, traverseContext);
releaseTraverseContext(traverseContext);
}

入参: children, forEachFunc, forEachContext.
首先通过 getPooledTraverseContext 拿到一个遍历的上下文对象 traverseContext,然后调用 traverseAllChildren 方法来遍历所有传入 children 的后代节点。
最后释放当前的 traverseContext.

阅读全文 »

引入的模块

1
2
3
4
5
var ReactNoopUpdateQueue = require("ReactNoopUpdateQueue");

var emptyObject = require("fbjs/lib/emptyObject");
var invariant = require("fbjs/lib/invariant");
var lowPriorityWarning = require("lowPriorityWarning");

其中, ReactNoopUpdateQueue 是默认的 updater ,用来提供 update, replaceState, setState 的入队操作,但可能是由于是默认 updater 的原因,只提供了 API 和对入参的校验,但没有提供实际的功能。比如:

阅读全文 »

本系列文章以 react 最新的版本: 16.0.0-beta.5 为准。
首先我们入口文件 ReactEntry.js 来看,

1
2
3
4
5
6
var ReactBaseClasses = require("ReactBaseClasses");
var ReactChildren = require("ReactChildren");
var ReactElement = require("ReactElement");
var ReactVersion = require("ReactVersion");

var onlyChild = require("onlyChild");

依赖模块:
• ReactBaseClasses
• ReactChildren
• ReactElement
• onlyChild
• ReactElementValidator
接着:

阅读全文 »

Redux 的源码非常的精炼,短短几百行代码,确提供了强大的功能。今天,我们就来一探究竟。

看源码最简单的方式,就是从入口文件来看,看它依赖哪些模块,然后在依次看这些模块的内容,最后也就对整个代码有个清晰的认识了。

所以我们就从入口文件开始来看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import createStore from "./createStore";
import combineReducers from "./combineReducers";
import bindActionCreators from "./bindActionCreators";
import applyMiddleware from "./applyMiddleware";
import compose from "./compose";
import warning from "./utils/warning";

/*
* This is a dummy function to check if the function name has been altered by minification.
* If the function has been minified and NODE_ENV !== 'production', warn the user.
*/
function isCrushed() {}
// 就是根据 isCrushed 是否被压缩了,来警告开发者正在非生产环境使用一个压缩过的代码。
if (
process.env.NODE_ENV !== "production" &&
typeof isCrushed.name === "string" &&
isCrushed.name !== "isCrushed"
) {
warning(
"You are currently using minified code outside of NODE_ENV === 'production'. " +
"This means that you are running a slower development build of Redux. " +
"You can use looseenvify (https://github.com/zertosh/looseenvify) for browserify " +
"or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) " +
"to ensure you have the correct code for your production build."
);
}

export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
};

可以看到它依赖了下面这几个模块:

  • createStore
  • combineReducers
  • bindActionCreators
  • applyMiddleware
  • compose
  • warning

其他没什么说的,就是把一些 API 暴露出去。那我们就先按照这个模块依赖顺序,依次进行解读。

createStore

阅读全文 »

做了哪些事情

说是一年,其实也一年多了,不过这倒不重要。主要看看做了哪些事吧:

1. 改造发布流程

刚来公司的时候,白领贷的前端是基于 Riot 框架开发的系统。语言层面,使用 ES6 和 Less 开发,发布到线上是需要一个构建打包和编译的过程,不能直接部署到线上服务器。

所以项目的开发流程就是,功能开发,提交代码,本地打包,打包后的代码跟源代码一起提交测试,而后合并代码到 master 然后再在本地打包,然后上线。

这个流程非常的不合理,因为源代码和编译打包后的代码放到一块管理,本身就不合理。经常会出现,你一打包测试,我这边代码推上去就一堆冲突。所以我就想怎么来改进这个流程。

改进方案一

网易内部是有一个 omad 系统(自动部署平台)的,想着说看能不能借助这个平台干点事情。因为初来乍到,对这个部署平台不是很熟悉,就问了和这个平台接触最多的测试同学,测试同学告诉我说 omad 没有提供任何对外的接口,所以我想的可能没法实现。

这样就尴尬了,没法做线上打包发布,那么打包这个过程就必然得放在本地来做。所以我就搞了一套现在看来很蠢的方案。那就是,用两个仓库来分别管理源代码和打包后的代码。

那么,如果需要发布的话,就需要在源代码仓库先进行打包编译,然后把打包编译的结果复制到管理要发布的代码的仓库。

一张示意图:

很明显,上面这种方式也只是临时解决方案,需要两个仓库来管理一份代码。每次提交代码都需要额外执行一个 run build 命令。

改进方案二:

由于上面的方案太蠢了,所以有了改进方案二,也是我们现在一直在用的方案:那就是直接线上打包。具体如下:

阅读全文 »

Overview

首先看一张图:

React-Redux

可以看到入口文件里面引入了这几个模块:

  • Provider
  • connectAdvanced
  • connect

我们就来依次看一下这几个模块

Provider

store 挂在在了 context 上面,给予了子组件访问 store 的能力。

阅读全文 »

技术选型

我们的项目主要选用以下技术开发,再配合一些其它辅助工具。

  • react
  • react-router
  • redux
  • react-redux

开发及线上环境

开发环境

由于项目是前后端分离的,所以我们需要一套完整的开发环境,需要包括以下功能:

  • 数据 Mock
  • Webpack 实施编译刷新
  • 方便前后端联调

基于这些需求,我们基于 Express, Webpack,Webpack-dev-middleware 搭建了这套完整的开发环境。

开发环境

可以看到,浏览器所有的请求都被本地的 Node.js 服务拦截。对于静态资源请求,都委托给 webpack-dev-middleware 来处理,对于接口请求根据不同的环境来决定要做的操作。

本地开发

ENV = 'development' 时,也就是开发环境,那么就直接读取本地的 mock 数据来渲染页面。

前后端联调

ENV = 'api' 时,也就是我们认为的联调环境,这个时候对于接口请求由 node.js 转发到需要联调的真实后端服务地址上,从而避免直接调用所产生的跨域问题。

这样就可以直接用本地开发代码和后端联调,能大大提高效率,省去了每次需要往服务器上构建部署的步骤。

线上环境

阅读全文 »

由于项目使用 React, React-Router, Redux 来开发项目,采取了前端渲染的方式(如果采用 ssr 就不会有这个问题)。

问题

所以每到一个页面,需要发一个初始化的请求来获取页面数据,然后重新渲染页面,所以之前一个页面从请求到加载完毕的流程是这样的:

1
Route match -> ComponentWillMount ->  render -> ComponentDidMount -> dispatch(init())->  render -> componentDidUpdate

首先是路由匹配,然后准备加载组件,使用通过 Reducer 里面的初始化 state 进行 render, 然后触发 ComponentDidMount 事件,在这个事件里面去 dispatch 一个执行页面初始化的请求的 Action,请求成功过以后触发组件的重新渲染。

可以看到,展示最终的页面需要组件重新渲染两次。一次是使用写死在前端的reducer 里面的 initialState 数据来渲染,一次是在拿到后端数据进行的渲染。

所以有的时候会出现闪屏的情况,用户体验很不好。

我们的要求是进页面以后就是用从后台获取的最新数据渲染的页面,整个页面只会 render 一次。

那么怎么解决这个问题呢?

解决方案

要解决这个问题,那么必然是加载好数据后,再去挂载页面组件,那么加载数据的时机就显得尤为重要。

阅读全文 »

最近对团队内部 React 组件库(ne-rc)中的 Form 组件进行了重构,记录一下思考的过程。

一些前置定义:

| 名词 | 定义 |
| —- | —- ||
| 表单 | Form 组件 |
| 子表单 | 嵌套在 Form 下面的类似 Input, Select 这样的子组件 |

首先我们看一下,我们的对 Form 组件的需求是什么。

  1. 获取当前变动表单的状态
    • 校验所有必填表单是否填写完成
    • 对外触发具体表单变化的方法 formFieldChange
  2. 暴露对外提供整个表单状态的方法
    • 提供整个表单最新状态的方法 $Form.data
  3. 提交方法
    • 校验表单是否通过校验
    • 对外触发 formSubmit 方法

接着我们从重构前和重构后,看如何来解决这个问题。

Before

获取当前变动表单的状态

如何获取变动的子表单

React 父子通信需要通过 prop 传递方法,对于 Form 下面的类似与 Input 之类的子表单的变化想要通知到父级,如果不借助第三方的事件传递方法,那么就只能通过由父级通过 props 向 Input 传递 formFieldChange(假设就叫这个名字)方法,然后当子组件变化时去调用 formFieldChange 来实现。

那么问题来了,什么时候去传递这个方法呢?

不能在具体页面里面使用的时候再去每条表单里面注册这个方法,那每个用到表单组件的时候就都需要给子表单进行这样的事件绑定,这样太累了。

所以一开始,我选择通过直接递归的遍历 Form 下面的 children,只要发现这个 children 是我想要的表单类型,那么就重新克隆一个带有 formFieldChange 的组件来替换掉原来的组件。

阅读全文 »

resolve.root,resolve.fallback,resolve.modulesDirectories

这些配置项项都被一个单独的配置所替代 resolve.modules. 查看 resolving 了解更多。

1
2
3
4
5
6
7
8
resolve: {
- root: path.join(__dirname, "src")
+ modules: [
+ path.join(__dirname, "src"),
+ "node_modules"
+ ]
}
 

resolve.extensions

这个选项已经不在需要传一个空字符串了。它的行为已经被移动到了 resolve.enforceExtension. 查看 resolving 了解更多。

resolve.*

这里修改了很多内容。因为被用到的不是很多,所以就不一一列举出来了。查看 resolving 了解更多。

module.loaders 现在是 module.rules

老的 loader 配置被一个更加强大的 rules 系统所取代了,允许配置 loader 和更多内容。为了保证一致性,旧的 module.loaders 语法依然可以合法且可以使用的。新的命名规范更加易于理解,这是一个使用 module.rules 去升级配置的很好的理由。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  module: {
- loaders: [
+ rules: [
{
test: /\.css$/,
- loaders: [
+ use: [
{
loader: "style-loader"
},
{
loader: "css-loader",
- query: {
+ options: {
modules: true
}
]
},
{
test: /\.jsx$/,
loader: "babel-loader", // Do not use "use" here
options: {
// ...
}
}
]
}
阅读全文 »