Сокращение дублирующегося кода

1. Две почти одинаковые функции follow и unfollow

1. Вот так эти две функции выглядели изначально в файле src/redux/users-reducer.js:

export const follow = (userId) => {

    return async (dispatch) => {
        dispatch(toogleFollowingProgress(true, userId));
        let data = await usersAPI.follow(userId)
            if (data.resultCode == 0) {
                dispatch(followSuccess(userId));
            } 
            dispatch(toogleFollowingProgress(false, userId));
     }
 }

 export const unfollow = (userId) => {
    return async (dispatch) => {
        dispatch(toogleFollowingProgress(true, userId));
           let data = await usersAPI.unfollow(userId)

                    if (data.resultCode == 0) {
                        dispatch(unfollowSuccess(userId));
                    }
                    dispatch(toogleFollowingProgress(false, userId));   
    }
 }

Как видно, большинство строк идентичны. Отличаются лишь 2 строки – экшнкриэейтеры (followSuccess и unfollowSuccess) и метода, импортируемый из файла src/api/api.js (usersAPI.follow(userId) и usersAPI.unfollow(userId)).

2. Поэтому создаем сразу “общую” функцию над этими двумя под названием followUnfollowFlow, а эти отличающиеся функции выносим в переменные внутри самих функций follow и unfollow, и вместо отличающихся названий указываем теперь имена самих переменных.

const followUnfollowFlow = async (dispatch, userId, apiMethod, actionCreator) => {
    dispatch(toogleFollowingProgress(true, userId));
    let data = await apiMethod(userId)
    if (data.resultCode == 0) {
        dispatch(actionCreator(userId));
    } 
    dispatch(toogleFollowingProgress(false, userId));
}

export const follow = (userId) => {

    return async (dispatch) => {
        let apiMethod = usersAPI.follow.bind(usersAPI);
        let actionCreator = followSuccess;

        /*dispatch(toogleFollowingProgress(true, userId));
        let data = await apiMethod(userId)
            if (data.resultCode == 0) {
                dispatch(actionCreator(userId));
            } 
            dispatch(toogleFollowingProgress(false, userId));*/
     }
 }

 export const unfollow = (userId) => {
    return async (dispatch) => {
        let apiMethod = usersAPI.follow.unfollow(usersAPI);
        let actionCreator = unfollowSuccess;

        /*dispatch(toogleFollowingProgress(true, userId));
           let data = await apiMethod(userId)

                    if (data.resultCode == 0) {
                        dispatch(actionCreator(userId));
                    }
                    dispatch(toogleFollowingProgress(false, userId));*/   
    }
 }

В итоге теперь эти небольшие отличия исчезли вовсе, так как мы вместо них поставили переменные с одинаковыми названиями. Полученный дублирующийся код (в примере выше он специально закомментирован) мы и поместили внутрь нашей “общей” функции, а значения, необходимые для переменных, положили в ее параметры вместе с приходящими dispatch, userId.

Кроме того обратите внимание, что мы забандили usersAPI.follow, когда клали его в перменную. Это на всякий случай, так как мы не знаем, будет ли этот метод вызывать какие-то свойства this, а вызывать мы теперь будем его “оторвано” как бы от самого объекта (т.е. точки не будет слева), поэтому контекст this может потеряться.

3. Теперь мы просто удаляем внутри самих функций дублирующийся код (который в примере выше закомментирован). И вместо него вызываем нашу “общую” функцию:

const followUnfollowFlow = async (dispatch, userId, apiMethod, actionCreator) => {
    dispatch(toogleFollowingProgress(true, userId));
    let data = await apiMethod(userId)
    if (data.resultCode == 0) {
        dispatch(actionCreator(userId));
    } 
    dispatch(toogleFollowingProgress(false, userId));
}

export const follow = (userId) => {

    return async (dispatch) => {
        let apiMethod = usersAPI.follow.bind(usersAPI);
        let actionCreator = followSuccess;
        followUnfollowFlow(dispatch, userId, apiMethod, actionCreator)
     }
 }

 export const unfollow = (userId) => {
    return async (dispatch) => {
        let apiMethod = usersAPI.follow.unfollow(usersAPI);
        let actionCreator = unfollowSuccess;
        followUnfollowFlow(dispatch, userId, apiMethod, actionCreator)
    }
 }

4. А чтобы еще больше сократить наш код, мы можем вовсене использовать эти переменные, а сразу их значения положить в параметры “общей” функции:

const followUnfollowFlow = async (dispatch, userId, apiMethod, actionCreator) => {
    dispatch(toogleFollowingProgress(true, userId));
    let data = await apiMethod(userId)
    if (data.resultCode == 0) {
        dispatch(actionCreator(userId));
    } 
    dispatch(toogleFollowingProgress(false, userId));
}

export const follow = (userId) => {
    return async (dispatch) => {
        followUnfollowFlow(dispatch, userId, usersAPI.follow.bind(usersAPI), followSuccess)
     }
 }

 export const unfollow = (userId) => {
    return async (dispatch) => {
        followUnfollowFlow(dispatch, userId, usersAPI.follow.unfollow.bind(usersAPI), followSuccess)
    }
 }

2. Почти одинаковый код внутри самого редьюсера “usersReducer” при помощи отдельной “функции-хелпер”

Создание "функций-хелперов" иногда бывает хорошим инструментом для рефакторинга дублирующегося кода.

1. В том же файле (src/redux/users-reducer.js) внутри редьюсера есть очень похожий код в конструкции switch:

const usersReducer = (state = initialState, action) => {
    switch(action.type) {
        case FOLLOW:
            return {
                ...state,
                users: state.users.map( u =>  {
                    if (u.id === action.userId) {
                        return {...u, followed: true}
                    }
                    return u;
                })
            }
        case UNFOLLOW:
            return {
                ...state,
                users: state.users.map( u =>  {
                    if (u.id === action.userId) {
                        return {...u, followed: false}
                    }
                    return u;
                })
            }
....

2. Чтобы его зарефакторить создаем отдельный файлик src/utils/object-helpers.js (рядом с папкой validators, внутри которой хранятся валидаторы для redux-form) с “функцией-хелпером” внутри, которая так же принимает в параметры переменные для повторяющихся элементов и другие, необходимые для работы + заменяет их названиями элементы внутри, которы отличались:

export const updateObjectInArray = (items, itemId, objPropName, newObjProps) => {
   return items.map( u =>  {
        if (u[objPropName] === itemId) {
            return {...u, ...newObjProps}
        }
        return u;
    })
}

Как видно, еще один из нюансов состоит в том, что мы на место followed: false помещаем объект и делаем его деструктуризацию. Т.е. в нем может быть одно свойство (как сейчас), а может и много.

3. Теперь, аналогично первому примеру, просто заменяем дублирующийся код нашей новой “общей” функцией, импортируя в файл-редьюсер:

import { updateObjectInArray } from "../utils/object-helpers";
...

const usersReducer = (state = initialState, action) => {
    switch(action.type) {
        case FOLLOW:
            return {
                ...state,
                users: updateObjectInArray(state.users, action.userId, 'id', {followed: true})
                /*users: state.users.map( u =>  {
                    if (u.id === action.userId) {
                        return {...u, followed: true}
                    }
                    return u;
                })*/
            }
        case UNFOLLOW:
            return {
                ...state,
                users: updateObjectInArray(state.users, action.userId, 'id', {followed: false})
                /*users: state.users.map( u =>  {
                    if (u.id === action.userId) {
                        return {...u, followed: false}
                    }
                    return u;
                })*/
            }
...

Дублирующийся код в примере выше закомментирован.

4. Теперь мы просто удаляем его:

const usersReducer = (state = initialState, action) => {
    switch(action.type) {
        case FOLLOW:
            return {
                ...state,
                users: updateObjectInArray(state.users, action.userId, 'id', {followed: true})
            }
        case UNFOLLOW:
            return {
                ...state,
                users: updateObjectInArray(state.users, action.userId, 'id', {followed: false})
            }
...

3. Сокращаем поля, созданные при помощи redux-form, вынося их в отдельную “функцию-хелпер”

1. Изначально наш код в компоненте Login по адресу src/Login/Login.jsx выглядел вот так:

const LoginForm = (props) => {
    return (
        <form onSubmit={props.handleSubmit}>
            <div>
                <Field placeholder={"Email"} name={"email"}
                       validate={[required]}
                       component={Input}/>
            </div>
            <div>
                <Field placeholder={"Password"} name={"password"} type={"password"}
                       validate={[required]}
                       component={Input}/>
            </div>
            <div>
                <Field component={Input} name={"rememberMe"} type={"checkbox"}/> remember me
            </div>
            { props.error && <div className={style.formSummaryError}>
                {props.error}
            </div>
            }
            <div>
                <button>Login</button>
            </div>
        </form>
    )
}

2. Создадим для полей Field, которые делались при помощи redux-form и выглядят очень похоже, отдельную функцию (это тоже “хелпер”), дающую возможность их заменить на нее. Находится она будет в файле src/components/common/FormsControls.js (в нем мы создавали и сами компоненты форм “Textarea” и “Input”).

export const createField = (placeholder, name, validators, component, props = {}, text = '') => (
    <div>
        <Field placeholder={placeholder} name={name}
                        validate={validators}
                        component={component}
                        {...props}
        /> {text}
    </div>
)

Как видно, оборачивающие div(ы) мы тоже положили внутрь этой компоненты, что еще больше позволит сокритить дублирующийся код.

3. Теперь наш код вывода полей формы в Login.jsx будет выглядеть вот так:

const LoginForm = ({handleSubmit, error}) => {
    return (
        <form onSubmit={handleSubmit}>
            {createField("Email", "email", [required], Input)}
            {createField("Password", "password", [required], Input, {type: "password"})}
            {createField(null, "rememberMe", [], Input, {type: "checkbox"}, 'remember me')}
            <div className={style.formSummaryError}>
                {error}
            </div>
            <div>
                <button>Login</button>
            </div>
        </form>
    )
}

4. Повторяющийся код в componentDidMount() и componentDidUpdate()

В примере ниже мы вынесли повторяющийся код отдельный метод и потом вызвали его в нужных местах componentDidMount() и componentDidUpdate():

class ProfileContainer extends React.Component {

    refrehsProfile() { //вспомогательный метод, чтобы код не дублировался в маунт и апдейт одинаковый
        let userId = this.props.match.params.userId;
        if (!userId) {
            userId = this.props.authorizedUserId;
            if (!userId) {
                this.props.history.push("/login");
            }
        } 
        this.props.getUserProfile(userId);
        this.props.getStatus(userId);
    }
   
   
    componentDidMount() {
        this.refrehsProfile();
    }

    componentDidUpdate(prevProps, prevState, snapshot) {
        if (this.props.match.params.userId != prevProps.match.params.userId) {
            this.refrehsProfile();
        }
    }
    
    
    render() { 
......

 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *