SSR 其实仅仅指的是 Render 的机制,而像我的 univera 以及其他类似的方案在你问的这个问题上涉及到的层面是 isomorphic app,也就是同构化(现在倾向于叫 universal 了)。
同构追求的是什么?同样的代码无分前后只写一次(且不需要逻辑判断当前是哪一个端)。
option 3,你不喜欢,我也不喜欢,so pass
option 2,它和 univera 的架构其实不同,它的 API 和 APP 是完全分离的,也就是你本地开发的时候需要开至少两个 server。在这个 repo 里,API 是 :3031, SSR 是 :3030。
这样子的架构就会产生一个问题,对于 Client 来说,它是不知道 API Endpoint 的具体位置的,当它发请求的时候就是直接请求 :3030/api/...
,然后 SSR 里有一个 proxy 代理到了 :3031 去;而对于 SSR Server 来说,如果是它发请求(比如首次请求的时候),它是需要直接访问 :3031/...
的。
因此,它的架构里需要一个 isServer
的判断来区分请求发生的来源:https://github.com/spikebrehm/isomorphic-tutorial/blob/master/app/api_client.js ,有了这个 wrapper 实际调用的时候才能做到“形似同构”。
option1,也就是我的 univera 则是 API 与 SSR 同在一起的,所以不管对 Client 还是对 SSR Server,'/api/...' 所代表的意义是完全一样的,不存在中转代理这么一说。因此只要你的 request library 支持 node/browser 同构调用,那么对于每个 endpoint 只需要唯一的一次请求就好了。所以你会看到每一个 container layer 都会一个 api.js 的唯一调用,那里是我们真正去向 API 发起请求的地方。
至于组件内部的 fetchData 方法,它的用处就是处理你最上面问的问题,多个 async request 怎么处理。在 SSR 那边,它应该完全“不知道”要处理的 API 请求是什么(具体的 endpoint,参数等等),它只需要在 request hit 到自己的时候替客户端调用一次请求方法就好了,也就是 component.fetchData()
这个静态方法,这个方法仅仅就是给 SSR 用的。
为什么要写 fetchData()
而不直接利用 componentDidMount()
里相同的 API 请求呢?这是因为当 SSR 走到准备数据的逻辑时,真正的 UI 组件还没有实例化,componentDidMount
还没被调用,而我们要先行一步给 UI rendering 提供 data。这是 React 的机制决定的。
另外还有一个原因和 Redux 有关,see,请求 API 不是终点,这只是一个开始,最终的目的是要 payload -> reducer -> store。这个过程,在 SSR 会为首次渲染提供必要的数据,在 Client 则是变更 state 通知相应的 UI 组件 update。
在 client 那边,有 react-redux 可以直接把 raw action 变成 dispatched action,所以我们在组件里可以直接 this.props.someAction()
去写,这样会让写组件变得很直观方便。
而在 ssr 那边,由于 fetchData 发生在组件渲染之间,我们只有 raw action,我必须手动用 store.dispatch 去 call actions,所以尽管 API 的调用是一样的,可是在推进 store 这件事情上,由于发生先后顺序的不同,不得不把这个区分开来。这其实和 isomorphic 无关,这是完全不同的渲染逻辑:
这两件事情的 life cycle 不同,且由于中间有一个 redux 的存在,才造成了 fetchData()
和 componentDidMount()
看起来非常相似的结果,但其实它们各自用在不同的 life cycle 里,并不冲突。
那么 option 1 和 option 2 殊途同归吧,最终都是为了 isomorphic,但是一个是 API/SSR 分离的,需要做请求代理,一个则不需要。这件事情本身并无高下,还是要看实际需求的。比如说如果你已经有了一个不是 node.js 写的 API service 怎么办?你没有机会把 API 和 SSR 整合在一起,那么你一样还是要有一个 api proxy,只不过写得好的话也不需要判断 isServer
或者 isBrowser
这样啰嗦的东东。
类似的问题其实需要你静下心来把完整的流程梳理的清清楚楚,然后明确什么环节应该处理什么任务,这个东西一旦确立了,也就找到解决问题的办法了,具体的实现过程一定会遇到些困难,那也是没办法的事情……至少我还没有做到我心目中的完美。
https://github.com/nightire/dotfiles
看最后一条,手机不好打字,抱歉。
“写 JS 不加分号”只是一个笼统的说法,真正做的时候并不是无脑全不写,要么像 @dd1994 所说的那样牢记简单的规则,要么永远都不要在行首写 ( / [
比如说你举的例子,我平时是这么写的:
// range 是一个工具函数,很多函数库都有提供,也可以自己实现一个简单的:
const range = (amount, start = 0) => [...Array(amount + start).keys()].splice(start)
let count = 123
range(10, 1).map(x => x)
// [1,2,3,4,5,6,7,8,9,10]
@afly 不太明白你说的意思,svg 就是 svg 了还需要什么库?用的就是原生的 svg 呀。
我猜你指的是交互的部分吧?比如说各种拖拽和连线?这个其实和 svg 没有紧耦和的关系,你用什么都可以的。你看到的问卷设计端是 angular 1 框架,各种交互都是直接 directives 去实现的,大部分拖拽连线都是自己写的,没有用第三方的库。(用也无妨,拖拽相关的 API 并不复杂)
使用 svg 的原因是本身它就是 xml,非常便于访问和操作,另外我们的视图是可以 10% - 200% 缩放的,只有 svg 能提供这么大范围的图形缩放而且不失真(本身就是矢量的)
@zhuxianglin ember-i18n 的文档还算齐全,不难看懂吧?https://github.com/jamesarosen/ember-i18n/wiki
我们用的是 ember-intl,所以你的问题还是看文档吧,我没有什么经验。
@1c7 差不多就是这个意思了,不过第 2 点我不确定你是否理解我说的带时间的链接,你可以看看这个视频:https://www.youtube.com/watch?v=xFTDNGZExuU
注意视频的描述,作者就是用带有时间节点的链接做的目录
其实呢,油管上的视频链接都可以追加时间节点的 parameter,用户点击这样的链接就会直接掉到正确的地方。于是油管上有很多热心人就在视频下面评论列出该视频的重要时间节点,其他人就方便啦。有些讲究的发布者也会把这些节点写在描述里。
你也可以这样做,一边看视频一边贴链接,想要汇总的话适用 API 把你的 comments 抓回来就是了。
#36 楼 @geniousli 这个可能性就多了,目前我们和有需要的客户采取按需定制接口的方式,以后我们会做开放 API 供各种外部集成应用的场景。
#33 楼 @geniousli 我们有做一些很其他平台集成的事情,不过我不太肯定你指的是什么,给个例子?
@hging 你好,简历已经收到,稍后电子邮件联系:)
#27 楼 @1272729223 不怪不怪,说起来有人信任我的技术或是判断,自己当然觉得很开心很感动,只可惜我没法到处去喊 xxx 最好,长命百岁,大家都去用呀……去评价很容易,要负责任就很难。
分享一篇文章给你:https://segmentfault.com/a/1190000004918131
说的是 Java 而不是前端,不过作者说的很多理念我觉得都是非常好的,和我们所讨论的话题非常贴切。
#22 楼 @1272729223 这个问题你不妨问问现在正在追捧 vue.js 的诸位兄弟好了,对我来说压根儿不关心谁死谁活的问题,也没有办法回答你,这么说吧:英雄都是应运而生的,技术环境的发展是有外在趋势的,前端这块现在就是发展的快,不可预测性高,这是任何个体都无法改变的客观事实,因此百花齐放,百鸟争鸣根本就不奇怪。然而总有一天这锅水会冷下来、稳定下来,只是我说不出是什么时候罢了,如果你现在不急,不妨安心坐下来慢慢观望。还是那句话,选择不是因为看谁活得长,“信徒”多,而是看自己眼下的需要。
精彩!👏🎊🎉
#18 楼 @flypiggys 动心就来嘛~我可以代表前端团队向各位保证俺们这边绝对让你们做的省心放心舒心。
react 是一个 view engine,它主要解决的问题是渲染性能和提供良好的 UI 组件构建系统——react 不是框架,它只算是一个框架中的一部分
redux 是一个 data store,它主要解决的问题是状态管理以及约定了一种 view <-> state 映射的模式——redux 也不是框架,它也只算是一个框架中的一部分
实际上它们是那种典型的“各自顾好各自的事情,至于连起来怎么用那就看你了”的哲学,所以你指望它们 out-of-box 替你解决框架层面的问题是问道于盲了。React 的体系没有框架,只有架构理念,我的意思是以 react 为中心的话,没有一个“标准的、官方的”框架体系,只有一个 data-flow 层面官方的理念(Flux)以及一堆民间的实践(Redux 是其中之一)。
So,你指望 react 和 redux 帮你解决框架的什么问题呢?
换不换框架或者换什么框架又或者要不要自己写/组装框架,这是一个实践性很强的话题,并没有放之四海而皆准的答案。我觉得吧,你可能就是那种抱着“找到一个 ~= 100% perfect 框架然后可以舒舒服服用一辈子”的想法的类型,然而我个人觉得这是不可能的。好吧,如果是那种做项目,一个项目赚一笔钱的团队或许可以,但是要做互联网产品的话这真的不可能,因为环境变化的速度太快了。
我们用 ng 也很熟练也很开心,技术上讲,现在能用 react 做到的我们一样能用 ng 做到(纯粹前端领域,SSR 什么的不算),上面的设计端就是很好的例子。现在我们主要用 react 写客户端是存在很多特定需求的原因的,并非单纯是 react 的“框架”比 angular 更优秀更开心这种很“无聊”的理由。事实上在我们内部还有很多项目,比如说后台的管理系统、部署/监控平台、APIs/微服务管理平台……这些东西我们都会根据需要选用不同的技术栈(Angular/Ember/Meteor/React 我们前端团队通吃),如果你问我那个更好,我还真的说不出来。我只能说杀一只鸡,牛刀有牛刀的用法,菜刀有菜刀的用法,我作为前端的架构师在技术选型的时候考虑业务模型和商业目标的事情要远比技术本身多。
要说学习的成本那是一定会有的,但这不是目的只是过程而已。很多目前觉得自己 angular 已经用的熟练用的开心的,扪心自问自己真的用的足够好了吗?难道就不需要继续学习了吗?到了某种 level 之后就会明白,换个框架无非就是换个写法或是换一套约定罢了,真正要学的东西永无止境,不管你用什么框架都是一样的。如果我们只是抱着“找到一个完美框架一劳永逸解决所有问题”的思路来决定技术栈的话,我相信我们永远也不可能达到这个目标,也永远不可能把我们想做的东西做到极致。
#15 楼 @1272729223 设计端做的最早,那时候 react 还没有太纯熟,所以就没来得及用上。答题的客户端是 react 的,而且之前还用 meteor 试过一版。
前一段时间我写了两篇 react ssr 的时候正是我们用 react 重构客户端的期间,后面我们就要逐渐重构老迈的 ng 所做的设计端和数据分析端了。
前端这边其实一直都对 API 不太满意,想着趁重构之时一起改善了吧,但我们后端开发人员的能力和精力就到这儿了,所以才要来诚邀大牛入伙儿啊!
#5 楼 @darkbaby123 我记得给你看过的嘛,快来吧快来吧!
其实配置 polipo 不需要这么麻烦,看看:https://github.com/nightire/dotfiles
Is she available...?
#5 楼 @darkbaby123 对呀,Object.observe()
是很狭隘没有远见的想法,所以被废弃是很正常的,当时还有一些人在幸灾乐祸呢,但 TC39 的人也不是傻子,对 UI 编程来说 Observable 是很重要的模式,他们怎么可能不重视。
#3 楼 @darkbaby123 Object.observe()
被废弃了,但是 Observable 没有。实际上是因为后者才导致前者被废弃的(很重要的一个原因),后者明显是更好的方案。
先把翻译给你:
当你为路由的 model
钩子提供了一个模型的时候,Ember 会把原始数组(users)转换为 ember array(这里最好能加上 Ember.Array
的 API 链接),(和原始数组相比)ember array 提供了 observation 以及诸如 pushObject
这样的便利方法。
以上是字面的翻译,里面有我附加的一些解释,可以自行斟酌处理。
稍微说点背景,JavaScript 语言所提供的原生集合类型(数组、对象等)都是很“单纯”的,你可以理解为最小化实现,只提供最基本的功能。但是做一个 UI 框架需要更多东西,所以就会去扩展这些原生集合类型,Ember.Array
就是这样一个代表,你可以把任意一个原生数组转变为 Ember.Array
而不损失任何原生特性,并且能够获得很多新的特性,比如上文提到的 observation 能力和 pushObject
方法等等。
因为在 Ember 的架构中这些“扩展”能力发挥着非常重要的作用,因此在很多地方 Ember 都会为你做隐式数据结构转换。上文说的就是比较重要的一个地方:路由的 model
钩子。如果此方法返回的是一个原生数组,那么为了确保接下来视图层获取的模型具有诸如 observation 等等必要的特性,Ember 会在这里隐式转换其为 Ember.Array
类型。
我不清楚这是早就有的隐式类型转换还是最近才加的,因为以前我都是习惯手动转换的:Ember.Array([])
或是用了 Ember Data 的话由 Data 层来负责处理这件事情,这个可以去看一下 model
hook 的源码历史记录。Anyway,自动的转换是好事,因为对于初学者来说不需要预先了解 Ember.Array
,在他看来数组就是数组并没有什么区别,只不过他也就错过了 Ember.Array
所提供的进阶能力,那么将来他会觉得这里面似乎有一些“黑魔法”是他在“野生”JavaScript 世界里所不曾遇见过的。
再补充一点进阶的,Ember.Array
其实是一个 mixin,真正常用的 High Level APIs 并不是它,而是 Ember.NativeArray
,而 NativeArray
又是扩展了 Ember.MutableArray
,Ember.Observable
,Ember.Copyable
这些 mixins 的,其中 Ember.MutableArray
才是扩展了 Ember.Array
和 Ember.MutableEnumerable
mixins 的……
这个对象树关系是很长,就跟我们学 Ruby 的对象关系树类似,但作为高阶框架的使用者,我们很少会直接去触碰这些相对低层的部分。拿 Ember 来说,集合类型如果直接处理原生的,那么 Ember 会在一些关键地方提供隐式转换,所以等到你在视图层(组件)真正使用它们的时候,它们都是“升级”过的——除非你就不按框架的约定来,这也是“约定”的体现之一;如果不直接处理原生集合,而是使用了 Ember Data 这样的高度抽象数据层,那么 Ember Data 自身就是由上述那些个基本类型和 mixins 组合而成,所以得到的结果也是一样的。