Eisen's Blog

© 2024. All rights reserved.

最近 web 开发的一些想法

2016 October-01

乱七八糟的 web 项目又做了有一阵子了,通过去探索和尝试各种好玩的框架和比较新的技术对目前很多东西有了更深的了解,这里写下一些最近的体会。

在评价各种技术的好坏之前,是想要说清楚自己从什么方面去衡量一个技术的好与坏。个人认为从技术与商业的关系上,如果非要分开的话,那么技术是必要服务于商业。商业的决策的执行受制于诸多因素,当然技术也是其中的一个方面。技术的灵活和高效支持了商业的灵活与高效。假如让商业和技术彻底的分开各自作为一个黑盒,那系统的运作应该是每次商业的变化都需要技术随之变化并且尽量快的完成这个变化,也就是说,我们需要的是一种能力,这个能力就是让商业决策付诸实施的速度尽量快的能力。这就要求技术去 拥抱变化,能够快速的响应需求,响应变化。那么,在技术的选型上就会有一个非常重要的标准就是开发效率了:如何完成同一个功能所用的时间尽量的少,当然这不是建立的偷工减料的基础上。当然对于形形色色的指标还有很多,比如稳定性,比如可扩展性,比如可靠性,比如安全性。但这些指标在现在的技术体系中已经逐渐在变成类似于基础设施的东西了,暂不考虑。

对比了一些主流的技术之后,如果从 get-started 的水平来讲,django ror 这样的框架非常的快。尤其是 rails 里面默认包含了当时 migration coffeescript scss 等等 web 开发的最佳实践,并有一个非常易于扩展的 plugin 体系,可以通过安装额外的 gem 就完成了一个个完善的功能。例如 devise 可以独当一面提供完整的用户系统,active-admin 则可以为软件提供一个完整的后台管理页面。如果是要做一个简单的项目的话,rails 绝对是最佳选择。最近的一个内部项目就需要快速的给财务的同学们搭建一个小的内部资产管理系统。通过 rails + active-admin +wice_grid 一周就搭建起来了这个体系,换用别的技术栈从头做起那简直是噩梦。

但是,rails 这种后端渲染的应用没办法兼顾目前诸多 client 的问题。并且在目前的发展过程中,我当年所期望的 html 统一天下的情况果真在逐渐成为现实。Mobile 端的 React Native,桌面端的 Electron,虽然这并不代表一次编写到处执行。它仅仅代表我可以采用 html 这种更简单的技术栈去替代复杂的技术栈进行开发了。那么,为了更好的支持多平台,将前后端分离后让一个后端可以支持所有的前端的方式胜过了同时提供 html render 以及 api 的模式。并且,它带来另外一个非常大的好处:separation of concern。这看似是将原来的逻辑分类转变成了仓库的物理分离,但这确实提供了并行开发的可行性,也让后端 API 的测试方便了许多。通过项目构建时将 RESTful API 作为前后端开发的契约可以保证双方开发不会出现太大的问题。

还有,简单 这个状态是带有时间戳的。现在的系统都很少有一锤子买卖,都可能有一个持续发展的过程。那么,一个一开始认为的 简单 的东西到底会变成什么样子真不好说。从维护的角度去讲,能够轻易的重构的强类型语言远胜过了弱类型的语言。那么,ruby python js 这种当年被认为是脚本语言的弊端就出现了。对我来讲 java 里面的 interface 和完善的 class 体系对于构建复杂的 oo 设计模式以及复杂的领域模型实在是太方便了,加上一些好的编辑器对 java 的增益效果,我更希望在业务逻辑可以无限复杂的后端采用这样的静态语言来屏蔽未来产品扩张时所带来的问题。甚至对于复杂的前端应用来说,大家也受不了弱语言 + 单线程的 js 了,typescript 在很大程度上填补了 js 的弊端,再加上快速的为 js 中添加 es6 甚至是 es7 的特性去屏蔽无结构、回调地狱这样一系列的问题。即便是 promise 也一直存在一个吞食内部报错的情况,实在是不好用。

而前端则倾向于采用 react angular 这样的框架去处理前端应用模块化、路由、异步、渲染等一系列的问题。但是这也减缓了页面第一次加载的速度:在第一个页面打开的时候需要加载大量的基础代码,这虽然可以通过分块加载以及初始页面的 server side render 在一定程度上有所缓解,但我总觉得这一定只是临时的解决方案。并且 server side render 也大大的增加了应用的复杂度,在我看来利大于弊。

UPDATE 目前像 next.js 采用的 server side render 和我在这里所说有所不同。next.js 希望实现 react component 直接在后端渲染,而我这里讲的是在 redux 中提及的首次 render 采用的技术。

总结一下呢,为了支持多平台以及更方便的对后端做 scale,前后端分离是一个不错的选择。并且在构建复杂的、或者说是需要持续维护的 web 产品的时候,如果可以尽量多的重用已有的东西,方便大家快速的认识很了解项目的内容,那么支持重构的强类型语言相对于动态语言有更多的优势。但是 rails 等动态语言确实很好用,我始终没有在 java 下看到像是 active-admin 这样的后台管理界面可以几个命令拔地而起。有的时候就在想是 java 这样的强类型语言这么多年为什么就没有做出来像 rails 那样的非常多的可重用模块呢?是自身的语法限制么?我深表怀疑。事实上,最近我也看到了 spring 下一些很有一些的东西包含了像是 active-record 的一些功能,但是又拥抱了 ddd 的思想,让持久化作为一个依赖出现在项目中。之后的日子里一方面会多多关注大型的 webapp 构建的问题以及在不同平台下前端的开发效率的问题,另一方面会看看 java 体系下的一些新进展,如果真的可以兼顾 get-started 的开发效率又能保证以后的维护问题,那么该体系会逐渐成为未来开发的主流。

当然,还有一些问题没有解决,比如部署,比如前后端的契约如何实施,比如 scale 等等。这个我理清了写吧.


Nodejs with babel es2015

2016 September-21

最近开始尝试用 nodejs 去写后端 api,和前端类似,为了采用 es6 的语法同样需要做一些 boilerplate 的工作。这里记录下来,加深一下记忆。

不过首先要先跑个题。其实 babel 不是第一个支持 js 变种的东西,最早出现过 coffeescript 目前比较流行的还有 typescript。typescript 支持强类型,支持 interface,尤其是 interface 这种 oo 的利器,真是让我跃跃欲试。但是有静静地想了想,其实目前 es6 作为一个未来的标准可能还是更有前途一些,并且目前的 es6 对于 oo 的支持已经相对来说好了一些了。应该还算凑合。

Install Dependencies

首先当然是创建项目,安装依赖了。

npm init -y
npm i -D babel-cli babel-preset-es2015 nodemon

其中 nodemon 是用来检测项目下的文件自动重启 nodejs server 的。

Create babelrc

然后创建一个 .babelrc 文件表明所支持的 babel 内容。

touch .babelrc

.babelrc:

{
  "presets": ["es2015"]
}

如果想要支持 Object Spread Operator 这样的功能(就是 ... 这个语法)则需要额外安装一个 babel 的插件 babel-plugin-transform-object-rest-spread

npm install babel-plugin-transform-object-rest-spread -D

然后在 .babelrc 添加如下内容

{
  "plugins": ["transform-object-rest-spread"]
}

Dev command to run es6 node js

在完成了 babel 的配置之后,我们就可以采用 es6 的语法去写 js 了。比如这里是一个样例。

index.js:

import http from 'http';

http.createServer((req, res) => {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end("Hello, World\n");
}).listen(300, '127.0.0.1');

这里用到了 es6 的 import 以及 => 方法。如果直接执行 node index.js 是会报错的。这里我们可以在 package.json 中的 scripts 下添加一个方法 devbabel-node 命令执行我们的 es6 语法的文件。

package.json:

{
  "script": {
    "dev": "babel-node index.js"
  }
}

然后通过用 nodemon 包裹 dev 命令可以做到自动重启 nodejs 的 server。

{
  "script": {
    "dev": "nodemon --exec babel-node index.js"
  }
}

Build command to create code for production

babel-node 仅仅是用于开发环境,每次修改代码后可以自动的编译文件。但是如果我们想要在生产环境部署代码的时候就需要一个专门的命令一次性将所有的代码编译成 node 支持的 es5 语法的文件了。

这里我们在 package.json 中再增加一个 build 命令

{
  "script": {
    "dev": "nodemon --exec babel-node index.js",
    "build": "babel src --out-dir dist"
  }
}

通过执行 npm run build 可以将 src 下的 es6 语法的文件编译成 dist 下支持 es5 语法的文件。

最后在添加一个 start 方法去启动 nodejs server。

{
  "script": {
    "dev": "nodemon --exec babel-node index.js",
    "build": "babel src --out-dir dist",
    "start" "node dist/index.js"
  }
}

为了保证每次执行 npm run start 命令前都会执行 build 命令,我们可以将 build 重命名为 prestartnodejs 会自动的帮助我们在执行 start 之前执行它。

{
  "script": {
    "dev": "nodemon --exec babel-node index.js",
    "prestart": "babel src --out-dir dist",
    "start" "node dist/index.js"
  }
}

参考

  1. Object Spread Operator
  2. nodemon
  3. Using ES6 and beyond with Node.js - node Video Tutorial

Redux with react router update

2016 August-17

之前有写过一篇 Redux With React Router,介绍 reduxreact-router 结合实现多个视图的 WebApp,但是最近才发现有很多地方已经和之前使用的方式不一样了,这里做一个更新。

use react-router without redux

react-router 提供了 react 的路由机制,在之前的文章中讲到了它可以和 react-router-redux 一起使用。当时的目的是为了将路由的信息传递到 redux store 中,在做 container componentconnect 时可以通过 mapStateToProps 的方式将路由中的信息提供给组件使用。但是随着 react-router 的不断更新以及 react-router-redux 的定位的不断明确,现在可以不适用 react-router-redux 而仅仅用 redux-router 完成将路由绑定到 container component。而 react-router-redux 成为了追朔包含了路由的用户行为的一个工具,而这个功能对于很多应用来说不是很有必要,其官方文档也强调:

This library is not necessary for using Redux together with React Router. You can use the two together just fine without any additional libraries. It is useful if you care about recording, persisting, and replaying user actions, using time travel. If you don't care about these features, just use Redux and React Router directly.

采用 withRouter 的高阶组件实现路由的绑定

首先,对于直接在 Router 中出现的组件,react-router 通过 context 的方式为该组件提供了当前路由的信息(如 params location 等)。但是如果是嵌套在路由创建的组件下的其他容器需要使用路由的信息呢?这个时候就需要用到 withRouter 这个由 react-router 提供的高阶组件了。在这里redux 的作者提供了一个视频教程介绍了这个方法。可是如果你直接 npm install react-router --save 之后就按着作者的来使用的话会发现根本没有 params 的参数...原因在于 withRouter 这个高阶参数在当前默认的最新版 react-router 中根本没有提供这个东西,它仅仅注入了 router 方便组件做导航而已...只有明确的指定 "react-router": "^3.0.0-alpha.1" 的版本之后才能这么做...坑爹呀。

参考

  1. How to use withRouter
  2. react-router
  3. react-router-tutorial