随着 Backbone 等老牌框架的逐渐衰退,前端 MVC 发展缓慢,有逐渐被 MVVM/Flux 所取代的趋势。
然而,纵观近几年的发展,可以发现一点,React/vue 和 Redux/Vuex 是分别在 MVC 中的 View 层和 Model 层做了进一步发展。如果 MVC 中的 Controller 层也推进一步,将得到一种升级版的 MVC,我们称之为 IMVC(同构 MVC)。
IMVC 可以实现一份代码在服务端和浏览器端皆可运行,具备单页应用和多页应用的所有优势,并且可以这两种模式里通过配置项进行自由切换。配合 Node.js、webpack、Babel 等基础设施,我们可以得到相比之前更加完善的一种前端架构。
isomorphic,读作[ˌaɪsə'mɔ:fɪk],意思是:同形的,同构的。
维基百科对它的描述是:同构是在数学对象之间定义的一类映射,它能揭示出在这些对象的属性或者操作之间存在的关系。若两个数学结构之间存在同构映射,那么这两个结构叫做是同构的。一般来说,如果忽略掉同构的对象的属性或操作的具体定义,单从结构上讲,同构的对象是完全等价的。
同构,也被化用在物理、化学以及计算机等其他领域。
isomorphic javascript(同构 js),是指一份 js 代码,既然可以跑在浏览器端,也可以跑在服务端。
www.slideshare.net/spikebrehm/…
同构 js 的发展历史,比 progressive web app 还要早很多。2009 年, node.js 问世,给予我们前后端统一语言的想象;更进一步的,前后端公用一套代码,也不是不可能。
有一个网站 isomorphic.net,专门收集跟同构 js 相关的文章和项目。从里面的文章列表来看,早在 2011 年的时候,业界已经开始探讨同构 js,并认为这将是未来的趋势。
可惜的是,同构 js 其实并没有得到真正意义上的发展。因为,在 2011 年,node.js 和 ECMAScript 都不够成熟,我们并没有很好的基础设施,去满足同构的目标。
现在是 2017 年,情况已经有所不同。ECMAScript 2015 标准定案,提供了一个标准的模块规范,前后端通用。尽管目前 node.js 和浏览器都没有实现 ES2015 模块标准,但是我们有 Babel 和 Webpack 等工具,可以提前享用新的语言特性带来的便利。
同构 js 有两个种类:「内容同构」和「形式同构」。
其中,「内容同构」指服务端和浏览器端执行的代码完全等价。比如:
- function add(a, b) {
- return a + b
- }
不管在服务端还是浏览器端,
函数都是一样的。
- add
而「形式同构」则不同,从原教旨主义的角度上看,它不是同构。因为,在浏览器端有一部分代码永远不会执行,而在服务端另一部分代码永远不会执行。比如:
- function doSomething() {
- if (isServer) {
- // do something in server-side
- } else if (isClient) {
- // do something in client-side
- }
- }
在 npm 里,有很多 package 标榜自己是同构的,用的方式就是「形式同构」。如果不作特殊处理,「形式同构」可能会增加浏览器端加载的 js 代码的体积。比如 React,它的 140+kb 的体积,是把只在服务端运行的代码也包含了进去。
同构不是一个布尔值,true 或者 false;同构是一个光谱形态,可以在很小范围里上实现同构,也可以在很大范围里实现同构。
我们今天所讨论的 isomorphic-mvc(简称 IMVC),是在 framework 层次上实现同构。
同构 js,不仅仅有抽象上的美感,它还有很多实用价值。
不使用同构方案,也可以用别的办法实现前两个的目标,但是别的办法却难以同时满足三个目标。
纯浏览器端渲染的问题在于,页面需要等待 js 加载完毕之后,才可见。
client-side renderging
www.slideshare.net/spikebrehm/…
服务端渲染可以加速首次访问的体验,在 js 加载之前,页面就渲染了首屏。但是,用户只对首次加载有耐心,如果操作过程中,频繁刷新页面,也会带给用户缓慢的感觉。
SERVER-SIDE RENDERING
www.slideshare.net/spikebrehm/…
同构渲染则可以得到两种好处,在首次加载时用服务端渲染,在交互过程中则采取浏览器端渲染。
从历史发展的角度看,同构确实是未来的一大趋势。
在 Web 开发的早期,采用的开发模式是:fat-server, thin-client
www.slideshare.net/spikebrehm/…
前端只是薄薄的一层,负责一些表单验证,DOM 操作和 JS 动画。在这个阶段,没有「前端工程师」这个工种,服务端开发顺便就把前端代码给写了。
在 Ajax 被发掘出来之后,Web 进入 2.0 时代,我们普遍推崇的模式是:thin-server, fat-client
- const app = createApp({
- type: 'createHistory',
- container: '#root',
- context: {
- isClient: true | false,
- isServer: false | true,
- ...injectFeatures
- },
- loader: webpackLoader | commonjsLoader,
- routes: routes,
- viewEngine: ReactDOM | ReactDOMServer,
- }) app.start() || app.render(url, context)
- const app = createApp(serverSettings)
- router.get('*', async (req, res, next) => {
- try {
- const { content } = await app.render(req.url, serverContext)
- res.render('layout', { content })
- } catch(error) {
- next(error)
- }
- })
- // routes
- export default [{
- path: '/demo',
- controller: require('./home/controller')
- }, {
- path: '/demo/list',
- controller: require('./list/controller')
- }, {
- path: '/demo/detail',
- controller: require('./detail/controller')
- }]
- ├──src // 源代码目录
- │├──app - demo // demo目录
- │├──app - abcd // 项目 abcd 平台目录
- ││├──components // 项目共享组件
- ││├──shared // 项目共享方法
- ││└──BaseController // 继承基类 Controller 的项目层 Controller
- ││├──home // 具体页面
- │││├──controller.js // 控制器
- │││├──model.js // 模型
- │││└──view.js // 视图
- ││├── * // 其他页面
- ││└──routes.js // abc 项目扁平化路由
- │├──app - * // 其他项目
- │├──components // 全局共享组件
- │├──shared // 全局共享文件
- ││└──BaseController // 基类 Controller
- │├──index.js // 全局 js 入口
- │└──routes.js // 全局扁平化路由
- ├──static // 源码 build 的目标静态文件夹
- class
- MyController
- extends
- BaseController
- {
- requireLogin = true // 是否依赖登陆态,BaseController 里自动处理
- View = View // 视图
- initialState = { count: 0 } // model 初始状态initialState
- actions = actions // model 状态变化的函数集合 actions
- handleIncre = () => { // 事件处理器,自动收集起来,传递给 View 组件
- let { history, store, fetch, location, context } = this // 功能分层
- let { INCREMENT } = store.actions
- INCREMENT() // 调用 action,更新 state, view 随之自动更新
- }
- async shouldComponentCreate() {} // 在这里鉴权,return false
- async componentWillCreate() {} // 在这里 fetch 首屏数据
- componentDidMount() {} // 在这里 fetch 非首屏数据
- pageWillLeave() {} // 在这里执行路由跳转离开前的逻辑
- windowWillUnload() {} // 在这里执行页面关闭前的逻辑
- }
- let EXEC_BY = (state, input) = >{
- let value = parseFloat(input, 10) return isNaN(value) ? state: {...state,
- count: state.count + value
- }
- }
- let EXEC_ASYNC = async(state, input) = >{
- await delay(1000) return EXEC_BY(state, input)
- }
- let store = createStore({
- EXEC_BY,
- EXEC_ASYNC
- },
- {
- count: 0
- })
- // webpack.config.js
- {
- test: /controller\.jsx?$/,
- loader: 'bundle-loader',
- query: {
- lazy: true,
- name: '[1]-[folder]',
- regExp: /[\/\\]app-([^\/\\]+)[\/\\]/.source
- },
- exclude: /node_modules/
- }
- // webpack.config.js
- output = {
- path: outputPath,
- filename: '[name]-[hash:6].js',
- chunkFilename: '[name]-[chunkhash:6].js'
- }
来源: https://juejin.im/post/5a14ffd15188253ee45b15bd