页面预览区
组件选择区
布局调整区:每个选中组件的属性编辑界面

可视化工具的产出

  • 就是一段描述当前页面布局与内容的 DSL,通常以 JSON 的格式存储

1 展示页面

应用场景:处理内容展示页面的需求
局限性

  • 无法动态更新页面数据
  • 适合服务端渲染,因为每个页面的渲染结果完全是数据驱动

JSON解析器&引擎

  1. DSL,描述当前页面布局与内容,通常以 JSON 的格式存储
  2. 客户端代码中还需要有两个解析器
    1. 路由解析器,根据当前页面路径向后端发送请求拿到对应页面的 DSL 数据
    2. 拿到DSL 数据后对 components 字段进行解析,然后把JSON渲染成页面组件

2 动态页面

动态更新数据,需要将组件的数据源与组件的配置解耦
将组件中配置好的数据,替换为一个后端的数据接口,
让后端的数据接口可以直接与组件进行对接

数据资产

  1. 将数据接口统一处理为数据资产
  2. 使用者配置组件的数据源时,让其可以在所有相关的数据资产中选择需要的部分,然后再转化为具体的数据接口,保存在组件配置中

应用场景

促销页、活动页等数据变动频繁的业务场景

withData高阶组件

解决的问题:何时发出这些异步的请求去获取数据?
使用高阶组件

  • 抽象出一个专门根据组件的 api 属性发送请求的高阶组件,
  • 并将它包裹在所有需要发送异步请求的组件之上
  1. function withData(WrapperComponent) {
  2. class FetchData extends Component {
  3. state = {
  4. data: {},
  5. }
  6. componentDidMount() {
  7. const { api } = this.props;
  8. $axios.get(api)
  9. .then((res) => {
  10. this.setState({ data: res.data });
  11. });
  12. }
  13. render() {
  14. return <WrapperComponent {...this.props} data={this.state.data} />;
  15. }
  16. }
  17. return FetchData;
  18. }

交互逻辑

参数联动,通过 query参数,来响应其他组件的变化
/api/shop/list?type=phone

实现组件之间的通信与简单交互,将不同的组件通过一些自定义的钩子 hook 起来
实现了组件与组件之间的联动,拓展了页面搭建工具可覆盖的需求范围
痛点:组件之间烦琐的依赖关系

JSON

  1. {
  2. "pageId": 1,
  3. "pageUrl": "/electronics",
  4. "pageTitle": "双十一大促 - 家电专场",
  5. "layout": "two-columns",
  6. "components": {
  7. "two-columns": {
  8. "firstCol": [{
  9. "componentName": "list",
  10. "api": "/api/electronics/list",
  11. "componentProps": [{
  12. "title": "促销家电列表"
  13. }]
  14. }],
  15. "secondCol": [{
  16. "componentName": "list",
  17. "api": "/api/electronics/list",
  18. "componentProps": [{
  19. "title": "促销家电列表"
  20. }]
  21. }]
  22. }
  23. }
  24. }
  25. const Router = ({ history }) => (
  26. <ConnectedRouter history={history}>
  27. <Route path="/home" component={Home} />
  28. <Route path="/store/:id" component={Store} />
  29. <Route component={DynamicRoute} /> // query the url from backend
  30. <Route path="/404" component={NotFound} />
  31. </ConnectedRouter>
  32. )

3 页面管理

管理可视化创建出来的页面

假设我们已经创建了一个营销页面的 MongoDB 集合,每个页面都有一个自己的 uuid,如 580d69e57f038c01cc41127e
简洁的做法,匹配路由

  • 在应用中创建一个例如 /production/:id 路由,根据每个页面的 uuid 来获取页面的 DSL 数据。

有意义的路由

uuid路由存在的问题:所有营销页的 url 都是无含义的 uuid,不利于 SEO,也不利于用户以输入 url 的方式到达页面

针对这个问题,需要在页面的 url 和 uuid 之间再建立起一个一一对应的关系

  • 后端要提供获取页面 DSL 数据的接口,
  • 还需要提供一个处理动态路由的接口
  • 如 580d69e57f038c01cc41127e 对应的页面 url 为 /production,
  • 在用户到达 /production 页面后,前端需要先将页面的 url /production发送至后端的动态路由接口,以拿到页面真正的 uuid,然后再调用获取页面 DSL 数据的接口拿到页面中配置好的组件数据并渲染

区分可视化路由

前端如何区分可视化的 /production/:id 动态路由和 /home 这种固定路由?
react-router 是按照所有路由定义的顺序逐一去匹配路由的,

如果当前的页面路径和所有的路由都匹配不上的话,则会渲染在最后定义的 404 页面。
应用中路由只分为两种,一种是定义好的固定路由,另一种是会由 404 页面统一处理的其他路由

引入了动态路由后,自定义的路由需要,和定义好的固定路由之间没有冲突,即

  • 如果应用中已经定义了 /home 的话,可视化页面的 url 就不能够再是 /home
  • 否则的话因为固定路由 /home 的匹配优先级较高,用户在到达 /home 页面后永远都只会看到固定路由 /home 的界面

在和所有固定路由尝试匹配失败后,不直接将当前 url 交给 404 页面处理,而是交给动态路由组件

  • 由动态路由组件尝试将当前 url 发送至后端的路由服务,查找当前 url 是否是页面集合中的一个有效 url,
  • 如果是:返回页面的 uuid,
  • 如果不是:返回查找失败,再由前端主动地将页面 url 替换为 /404 并渲染 404 页面

对于所有无法和固定路由相匹配的 url,都先假定它是一个动态路由,
尝试调用后端的路由服务来获取页面数据,如果后端的路由服务也查找不到它的话,再将其认定为是 404。

4 后端路由

在动态路由这样一个场景下,单纯前端路由的灵活度是远远不够的

路由分发器

路由分发器,根据页面类型的不同,分别调用不同的页面渲染服务
以多种类型页面的方式来组成一个完整的前端应用

项目脚手架,及后续的打包、发布,与页面可视化之间是没有直接的联系的
将二者完全拆分开,当成两个独立的工具分别开发

  • 一个解决独立页面开发的工具
    • 配置完了应用中所有的页面后
    • 只包含页面的具体内容,不包含菜单、页眉、页脚等全局组件
  • 一个应用构建工具,将配置好的页面嵌入应用路由中
    • 选择性地开启一些全局功能,如页面布局、权限配置、菜单管理等
  • 最终将配置好的应用通过 webpack 等打包工具编译成生产环境中可以运行的 HTML、CSS 和 JavaScript,再通过持续集成工具打上版本 tag 发布到服务器上。