页面预览区
组件选择区
布局调整区:每个选中组件的属性编辑界面
可视化工具的产出
- 就是一段描述当前页面布局与内容的 DSL,通常以 JSON 的格式存储
1 展示页面
应用场景:处理内容展示页面的需求
局限性
- 无法动态更新页面数据
- 适合服务端渲染,因为每个页面的渲染结果完全是数据驱动
JSON解析器&引擎
- DSL,描述当前页面布局与内容,通常以 JSON 的格式存储
- 客户端代码中还需要有两个解析器
- 路由解析器,根据当前页面路径向后端发送请求拿到对应页面的 DSL 数据
- 拿到DSL 数据后对 components 字段进行解析,然后把JSON渲染成页面组件
2 动态页面
动态更新数据,需要将组件的数据源与组件的配置解耦
将组件中配置好的数据,替换为一个后端的数据接口,
让后端的数据接口可以直接与组件进行对接
数据资产
- 将数据接口统一处理为数据资产
- 使用者配置组件的数据源时,让其可以在所有相关的数据资产中选择需要的部分,然后再转化为具体的数据接口,保存在组件配置中
应用场景
促销页、活动页等数据变动频繁的业务场景
withData高阶组件
解决的问题:何时发出这些异步的请求去获取数据?
使用高阶组件
- 抽象出一个专门根据组件的 api 属性发送请求的高阶组件,
- 并将它包裹在所有需要发送异步请求的组件之上
function withData(WrapperComponent) {
class FetchData extends Component {
state = {
data: {},
}
componentDidMount() {
const { api } = this.props;
$axios.get(api)
.then((res) => {
this.setState({ data: res.data });
});
}
render() {
return <WrapperComponent {...this.props} data={this.state.data} />;
}
}
return FetchData;
}
交互逻辑
参数联动,通过 query参数,来响应其他组件的变化
/api/shop/list?type=phone
实现组件之间的通信与简单交互,将不同的组件通过一些自定义的钩子 hook 起来
实现了组件与组件之间的联动,拓展了页面搭建工具可覆盖的需求范围
痛点:组件之间烦琐的依赖关系
JSON
{
"pageId": 1,
"pageUrl": "/electronics",
"pageTitle": "双十一大促 - 家电专场",
"layout": "two-columns",
"components": {
"two-columns": {
"firstCol": [{
"componentName": "list",
"api": "/api/electronics/list",
"componentProps": [{
"title": "促销家电列表"
}]
}],
"secondCol": [{
"componentName": "list",
"api": "/api/electronics/list",
"componentProps": [{
"title": "促销家电列表"
}]
}]
}
}
}
const Router = ({ history }) => (
<ConnectedRouter history={history}>
<Route path="/home" component={Home} />
<Route path="/store/:id" component={Store} />
<Route component={DynamicRoute} /> // query the url from backend
<Route path="/404" component={NotFound} />
</ConnectedRouter>
)
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 发布到服务器上。