Решение отсутствия prevProps в getDerivedStateFromProps

от автора

Привет, друзья!
Итак, разработчики Реакта решили сделать нашу работу с их либой более линейной, направить, так сказать, нас нерадивых на путь наименьшего шанса ошибиться и написать плохой код, что, на мой взгляд, является нашим неотъемлемым правом и способом совершенствоваться и изобретать. Речь идет о всеми любимых методах componentWillReceiveProps и других из той же серии, их больше не будет, но нам дадут альтернативу в виде статического метода getDerivedStateFromProps. Лично мне он напоминает темную комнату, где лежат вещи, и их нужно найти, но ничего не видно.

Разработчики в своих ответах на гневные комментарии пользователей Реакта пишут мол: Ну не дадим мы вам prevProps, это невозможно, придумайте что-нибудь, prevProps нет, ну вы держитесь там, просто кешируйте их в состоянии, в общем предлагают нам сделать небольшой костылек в нашем новом хорошем коде. Это все конечно несложно, можно понять и простить, но вот меня раздосадовал тот факт, что теперь у меня нет контекста this, комнату мою замуровали, из нее ничего не видно, даже соседей не слышно, вот и решил я написать для себя штуку, которая скроет в себе все костыли и мой код будет с виду хоть и странным, но бескостыльным (а бескостыльным ли?).

В общем, мне нужно внедрить prevProps в состояние компонента, еще хочется чтобы все выглядело как обычно, а также невозможно прожить без волшебного this в статическом getDerivedStateFromProps (вот дурак!). Два дня мучений и самосовершенствования и все готово, я родил мышь.

Установка

npm install --save state-master

Использование

Просто пишем такие же getDerivedStateFromProps и componentDidUpdate, но уже модифицированные.
Оборачиваем наш компонент в «withStateMaster», передаем туда список «пропсов», изменения которых нужно отслеживать

import {Component} from 'react' import {withStateMaster, registerContext, unregisterContext} from 'state-master';  // Список "пропсов", изменения которых нужно отслеживать const PROP_LIST = ['width', 'height', 'bgColor', 'fontSize', 'autoSize']; // или просто строка, если только одно значение const PROP_LIST = 'value';  // добавление начального состояния опционально const INITIAL_STATE = {   width: 1000,   height: 500 };  class ContainerComponent extends Component {   static displayName = 'Container';    static getDerivedStateFromProps(data) {     const {         nextProps,         prevProps,         state,         isInitial,         changed,         changedProps,         isChanged,         add,         addIfChanged,         isChangedAny,         addIfChangedAny,         isChangedAll,         call,         get       } = data;        // ниже пойдет речь об изменившихся пропсах, это только те, которые были указаны в массиве PROPS_LIST       // метка о том, что это первый вызов после конструктора       if (isInitial) {         // добавляем поле "name" с нужным значением "value" к возвращаемому изменению состояния         add('name', value);         // добавляем поле "name" со значением взятым из пришедших пропсов         add('name');               }        // changedProps это массив, который содержит имена всех поменявшихся пропсов       if (changedProps.indexOf('value') !== -1) {         add('value');        }        // возвращает true если данный prop как-либо изменился       if (isChanged('autoSize')) {         add('autoSize');       }             // возвращает true если данный prop изменился на указанное значение (здесь на true)       if (isChanged('autoSize', true)) {         add('autoSize', true);       }        // changed является true, если один из пропсов как-либо изменился       if (changed) {         add('somethingChanged', true);       }        // возвращает true, если один из пропсов как-либо изменился       // работает так же, как и пример выше       if (isChangedAny()) {          add('somethingChanged', true);       }        // возвращает true, если один из указанных пропсов как-либо изменился       if (isChangedAny('bgColor', 'fontSize', ...)) {         const {bgColor, fontSize} = nextProps;         add('style', {bgColor, fontSize});       }        // возвращает true, если все пропсы из списка PROPS_LIST как-либо изменились       if (isChangedAll()) {         add('allChanged', true);       }        // возвращает true, если все из указанных пропсов как-либо изменились       if (isChangedAll('width', 'height', ...)) {         const {width, height} = nextProps;         add('size', width + 'x' + height);          // вызывает функцию с таймаутом         // то же самое, что и setTimeout(() => this.changeSomething(), 0);         // используйте для каких-либо действий, которые нужно выполнить по завершению апдейта компонента         // хотя правильнее располагать этот вызов в componentDidUpdate         call(() => {           this.initNewSizes(width, height);         });       }        // вызывает метод "add", если указанный prop как-либо изменился       addIfChanged('name', value);       addIfChanged('name');        // вызывает метод "add", если какой-либо prop из списка PROPS_LIST как-либо изменился       addIfChangedAny('name', value);       addIfChangedAny('name');        // возвращает объект изменения состояния или null       // нужно для отладки, чтобы знать, что ушло в состояние       // располагайте в конце       console.log(get());        // если вы использовали метод "add", то возвращать ничего не нужно       // или вы можете просто вернуть объект, как и обычно без всяких вызовов "add"       return {         size: nextProps.width + 'x' + nextProps.height       }   }    constructor(props) {     super(props);     // используйте "registerContext", если вам необходим this контекст в getDerivedStateFromProps     // если компонент наследуется от другого, в котором был вызван "registerContext", то здесь этого делать не нужно     registerContext(this);   }    // данный метод также будет модифицирован   componentDidUpdate(data) {     const {         prevProps,         prevState,         snapshot,         changedProps,         changed,         isChanged,         isChangedAny,         isChangedAll       } = data;        if (isChanged('value')) {         const {value} = this.props;         this.doSomeAction(value);       }   }    componentWillUnmount() {     // также добавляйте этот код, если "registerContext" был вызван в конструкторе     unregisterContext(this);   }    render() {     const {style, size} = this.state;     return (       <div className="container" style={style}>         Size is {size}       </div>     )   } }  export const Container = withStateMaster(ContainerComponent, PROP_LIST, INITIAL_STATE);

Если компонент наследуется от другого, передайте родителя, чтобы родительский getDerivedStateFromProps был вызван

export const Container = withStateMaster(ContainerComponent, PROP_LIST, null, ParentalComponent);

Такого мое решение данной проблемы (хотя возможно я недостаточно понял реакт, и это вовсе не проблема)
Таким образом я вступил в сопротивление новым канонам Реакта, возможно когда-нибудь я смирюсь и перепишу все как надо.
Хотя разработчики возможно опять все переделают и возникнут другие насущные вопросы.
Всё, я ложусь, а лежачих, как говорится, не бьют))


ссылка на оригинал статьи https://habr.com/post/421615/


Комментарии

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

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