Как написать интерфейсный бизнес-код?

JavaScript

предисловие

Как написать поддерживаемый и читаемый код всегда было проблемой, которая мучила многих людей. О том, как называть переменные и как оптимизировать, если еще и другие советы, я не буду их здесь приводить.Рекомендуется прочитать "Энциклопедию кода 2". Есть тысячи книг, не хуже "Энциклопедии кода 2".

С тех пор, как я начал работать, я писал несколько страниц с повторяющимися и сложными взаимодействиями, и я не систематизировал свои собственные идеи.Эта статья представляет собой некоторый опыт, который я обобщил в проекте за последние полтора года.

Слоистый

Что касается бизнес-кода, большинство интерфейсных приложений по-прежнему фокусируются на отображении данных, что представляет собой не что иное, как получение данных из интерфейса, форматирование ряда данных и их отображение на странице.

Прежде всего, он должен быть максимально многослойным Традиционное многоуровневое MVC очень подходит для фронтенд-разработки, но для сложных страниц с увеличением бизнес-логики это часто вызывает проблему раздутых контроллеров. Поэтому помимо этого контроллер можно разделить на форматер, сервис и так далее.

Ниже приведена простая структура каталогов после наслоения.

    + pages
        + hotelList
            + components
                + Header.jsx
            + formatter
                + index.js
            + share
                + constants.js
                + utils.js
            + view.js
            + controller.js
            + model.js

Service

Унифицированное управление всеми путями запросов и инкапсуляция сетевых запросов, задействованных на странице, в виде классов.

// 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

Уровень форматирования хранит некоторые методы для форматирования данных.Эти методы принимают данные и возвращают новые данные.Другая логика не должна быть задействована, что способствует модульному тестированию. Одна функция форматирования не должна форматировать слишком много данных, а функции должны быть надлежащим образом разделены в соответствии с функцией и разумно повторно использоваться.

mvc

Как следует из названия, контроллер — это c в mvc, и контроллер должен быть местом для обработки различных операций с побочными эффектами (сетевые запросы, кеши, ответы на события и т. д.).

При обработке запроса контроллер вызовет соответствующий метод в сервисе, а затем вызовет метод форматирования после получения данных, сохранит отформатированные данные в хранилище и отобразит их на странице.

class Controller {
    fetchHotelList = () => 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();
        }
    }
}

Представление относится к реактивным компонентам.Рекомендуется как можно больше использовать чистые функциональные компоненты.С хуками реакция станет более чистой (на самом деле, stateful-компоненты также можно рассматривать как структуру mvc, состояние — это модель, рендеринг — это представление, каждый метод обработчика является контроллером).

Для реагирования самый внешний уровень обычно называется компонентом-контейнером, и мы будем выполнять операции с побочными эффектами, такие как сетевые запросы, в компоненте-контейнере.

Здесь часть логики в компоненте-контейнере также может быть удалена и помещена в контроллер (в данном случае это react-imvc), чтобы контроллеру можно было дать жизненный цикл, а компонент-контейнер использовался только для чистого отображения.

Мы помещаем жизненный цикл компонента-контейнера в обертку, компонент более высокого порядка, и вызываем внутри жизненный цикл, инкапсулированный в контроллере, чтобы мы могли написать более чистое представление, например:

wrapper.js

// wrapper.js(伪代码)
const Wrapper = (view) => {
    return class extends Component {
        constructor(props) {
            super(props)
        }
        componentWillMount() {
            this.props.pageWillMount && this.props.pageWillMount()
        }
        componentDidMount() {
                this.props.pageDidMount && this.props.pageDidMount()
            }
        }
        componentWillUnmount() {
            this.props.pageWillLeave && 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() {
        
    }
}

разное

Для закопанной точки ее тоже следует размещать в контроллере, но можно и отдельно слой трассировки.Что касается того, как реализовать и вызывать слой трассировки, то это зависит от личных предпочтений.Я предпочитаю использовать форму публикации и подписаться.

Если задействован еще и кеш, то можно выделить еще слой хранения, где хранятся различные операции по добавлению, удалению, проверке и модификации кеша.

Для некоторых часто используемых фиксированных значений их также можно поместить в константы.js, а значения получить путем введения констант, что удобно для последующего сопровождения.

// 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 и так далее.

Ниже находится элемент быстрого фильтра, который соответствует элементу фильтра подкатегории в некоторых меню элементов фильтра.

image_1d6dgvgio1hfg82u1kmo1u141tv69.png-230.3kB

Когда мы выбираем двуспальную кровать в фильтре, двуспальная кровать внизу будет выбрана по умолчанию.Наоборот, когда мы выбираем двуспальную кровать внизу, двуспальная кровать в категории фильтра также будет выбрана, и name будет возвращено исходному фильтру вышестоящего уровня.

image_1d6dgvscipuea6nsnhl6a1ijam.png-231.3kB

image_1d6dhiup11mnj12351f0fl36msh1g.png-57.7kB

Кроме того, после того, как мы нажмем на поле поиска и введем «двуспальная кровать», ассоциативное слово появится как двуспальная кровать, что указывает на то, что это элемент фильтра.Если пользователь выбирает эту двуспальную кровать, нам все равно нужно выбрать фильтр элемент и элемент быстрого фильтра по умолчанию.

Все эти три места связаны с фильтрацией элементов, и если изменить одно, будут изменены и два других, не говоря уже о том, что данные трех поступают из трех разных данных интерфейса, что за боль в заднице!

image_1d6dhc833375eo118vq1od61j6u13.png-40.4kB

Я использую этот пример, чтобы проиллюстрировать, что прежде чем приступить к написанию страницы, вы должны быть знакомы со скрытым взаимодействием и потоком данных на странице, и вам необходимо разработать более разумную структуру данных.

Для структуры глубокого списка пара ключ-значение будет быстрее, чем запрос массива, и связать с другими данными через ключ будет проще, но порядок не может быть гарантирован, и иногда может потребоваться пожертвовать пространством для время.

// 假设筛选项床型type为1,大床id为1,双床id为2.
const bed = {
    '1-1': {
        name: '大床',
        id: 1,
        type: 1
    },
    '1-2': {
        name: '双床',
        id: 2,
        type: 1
    }
}
const bedSort = ['1-1', '1-2'] // 保证展示顺序

Когда мы выбираем большую кровать, нам нужно только сохранить ключ «1-1», а затем сопоставить его с ключом в списке элементов быстрого фильтра в магазине (элемент в элементе быстрого фильтра также должен быть отформатирован как { 'type-id' : filterItem} формат пары ключ-значение), поэтому с точки зрения временной сложности это более эффективно, чем прямой обход двух массивов.

Суммировать

Прежде чем начать писать бизнес, вы должны четко продумать требования и бизнес-логику, спроектировать разумную структуру данных и хорошо распределить код, чтобы вы могли писать более удобный для сопровождения код в определенной степени.