赞
踩
点击上方 程序员成长指北,关注公众号
回复1,加入高级Node交流群
原本只是想简单群发一下,但是预览之后看到格式不友好,还是简单写一篇文章吧,这是我工作一年半来自己总结出来的一些经验。
对于业务代码来说,大部分的前端应用都还是以展示数据为主,无非是从接口拿到数据,进行一系列数据格式化后,显示在页面当中。
首先,应当尽可能的进行分层,传统的mvc分层很适用于前端开发,但对于复杂页面来说,随着业务逻辑增加,往往会造成controller臃肿的问题。因此,在此之上,可以将controller其分成formatter、service等等。
下面这是一些分层后简单的目录结构。
- + pages
- + hotelList
- + components
- + Header.jsx
- + formatter
- + index.js
- + share
- + constants.js
- + utils.js
- + view.js
- + controller.js
- + model.js
统一管理所有请求路径,并且将页面中涉及到的网络请求封装为class。
- // api.js
- export default {
- HOTELLIST: '/hotelList',
- HOTELDETAIL: '/hotelDetail'
- }
-
- // Service.js
- class Service {
- fetchHotelList = (params) => {
- return fetch(HOTELLIST, params);
- }
- fetchHotelDetail = (params) => {
- return fetch(HOTELLIST, params);
- }
- }
- export default new Service
-
-
这样带来的好处就是,很清楚的知道页面中涉及了哪些请求,如果使用了TypeScript,后续某个请求方法名修改了后,在所有调用的地方也会提示错误,非常方便。
formatter层储存一些格式化数据的方法,这些方法接收数据,返回新的数据,不应该再涉及到其他的逻辑,这样有利于单元测试。单个format函数也不应该格式化过多数据,函数应该根据功能进行适当拆分,合理复用。
顾名思义,controller就是mvc中的c,controller应该是处理各种副作用操作(网络请求、缓存等等)的地方。
当处理一个请求的时候,controller会调用service里面对应的方法,拿到数据后再调用formatter的方法,将格式化后的数据存入store中,展示到页面上。
- class Controller {
- fetchAndSaveHotelList = () => async (dispatch) => {
- const params = {}
- this.showLoading();
- try {
- const res = await Service.fetchHotelList(params)
- const hotelList = formatHotelList(res.Data && res.Data.HotelList)
- dispatch({
- type: 'UPDATE_HOTELLIST',
- hotelList
- })
- } catch (err) {
- this.showError(err);
- } finally {
- this.hideLoading();
- }
- }
- }
view则是指react组件,建议尽量用纯函数组件,有了hooks之后,react也会变得更加纯粹(实际上有状态组件也可以看做一个mvc的结构,state是model,render是view,各种handler方法是controller)。
对于react来说,最外层的一般称作容器组件,我们会在容器组件里面进行网络请求等副作用的操作。
在这里,容器组件里面的一些逻辑也可以剥离出来放到controller中(react-imvc就是这种做法),这样可以给controller赋予生命周期,容器组件只用于纯展示。
我们将容器组件的生命周期放到wrapper这个高阶组件中,并在里面调用controller里面封装的生命周期,这样我们可以就编写更加纯粹的view,例如:
wrapper.js
- // wrapper.js(伪代码)
- const Wrapper = (components) => {
- return class extends Component {
- constructor(props) {
- super(props)
- }
- componentWillMount() {
- this.props.pageWillMount()
- }
- componentDidMount() {
- this.props.pageDidMount()
- }
- componentWillUnmount() {
- this.props.pageWillLeave()
- }
- render() {
- const {
- store: state,
- actions
- } = this.props
- return view({state, actions})
- }
- }
- }
-
- view.js// view.js
- function view({
- state,
- actions
- }) {
-
- return (
- <>
- <Header
- title={state.title}
- handleBack={actions.goBackPage}
- />
- <Body />
- <Footer />
- </>
- )
- }
- export default Wrapper(view)
-
-
- controller.js// controller.js
- class Controller {
- pageDidMount() {
- this.bindScrollEvent('on')
- console.log('page did mount')
- }
- pageWillLeave() {
- this.bindScrollEvent('off')
- console.log('page will leave')
- }
- bindScrollEvent(status) {
- if (status === 'on') {
- this.bindScrollEvent('off');
- window.addEventListener('scroll', this.handleScroll);
- } else if (status === 'off') {
- window.removeEventListener('scroll', this.handleScroll);
- }
- }
- // 滚动事件
- handleScroll() {
- }
- }
-
-
对于埋点来说,原本也应该放到controller中,但也是可以独立出来一个tracelog层,至于tracelog层如何实现和调用,还是看个人爱好,我比较喜欢用发布订阅的形式。
如果还涉及到缓存,那我们也可以再分出来一个storage层,这里存放对缓存进行增删查改的各种操作。
对于一些常用的固定不变的值,也可以放到constants.js,通过引入constants来获取值,这样便于后续维护。
- // constants.js
- export const cityMapping = {
- '1': '北京',
- '2': '上海'
- }
- export const traceKey = {
- 'loading': 'PAGE_LOADING'
- }
-
- // tracelog.js
- class TraceLog {
- traceLoading = (params) => {
- tracelog(traceKey.loading, params);
- }
- }
- export default new TraceLog
-
- // storage.js
- export default class Storage {
- static get instance() {
- //
- }
- setName(name) {
- //
- }
- getName() {
- //
- }
- }
-
-
- 数据与交互
不过也不代表着这样写就够了,分层只能够保证代码结构上的清晰,真正想写出好的业务代码,最重要的还是你对业务逻辑足够清晰,页面上的数据流动是怎样的?数据结构怎么设计更加合理?页面上有哪些交互?这些交互会带来哪些影响?
以如下酒店列表页为例,这个页面看似简单,实际上包含了很多复杂的交互。
上方的是四个筛选项菜单,点开后里面包含了很多子类筛选项,比如筛选里面包括了双床、大床、三床,价格/星级里面包含了高档/豪华、¥150-¥300等等。
下方是快捷筛选项,对应了部分筛选项菜单里面的子类筛选项。
当我们选中筛选里面的双床后,下方的双床也会被默认选中,反之当我们选中下方的双床后,筛选类别里面的双床也会被选中,名称还会回显到原来的筛选上。
除此之外,我们点击搜索框后,输入'双床',联想词会出现双床,并表示这是个筛选项,如果用户选中了这个双床,我们依然需要筛选项和快捷筛选项默认选中。
这三个地方都涉及到了筛选项,并且修改一个,其他两个地方就要跟着改变,更何况三者的数据来自于三个不同的接口数据,这是多么蛋疼的一件事情!
我借助这个例子来说明,在开始写页面之前,一定要对页面中的隐藏交互和数据流动很熟悉,也需要去设计更加合理的数据结构。
对于深层次的列表结构,键值对会比数组查询速度更快,但是却不能保证顺序,这个时候就需要牺牲空间来换时间。
总结
在开始写业务之前,理应先想清楚需求和业务逻辑,设计出合理的数据结构,对代码进行好的分层,这样在一定程度上可以写出可维护性更高的代码。
代码例子:目前对前半段写了一个简单的例子,有需要的可以学习下:https://github.com/yinguangyao/mvc-demo
如果觉得这篇文章还不错
点击下面卡片关注我
来个【分享、点赞、在看】三连支持一下吧
“分享、点赞、在看” 支持一波
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。