Привет, друзья!
Итак, разработчики Реакта решили сделать нашу работу с их либой более линейной, направить, так сказать, нас нерадивых на путь наименьшего шанса ошибиться и написать плохой код, что, на мой взгляд, является нашим неотъемлемым правом и способом совершенствоваться и изобретать. Речь идет о всеми любимых методах 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);
Такого мое решение данной проблемы (хотя возможно я недостаточно понял реакт, и это вовсе не проблема).
Таким образом я вступил в сопротивление новым канонам Реакта, возможно когда-нибудь я смирюсь и перепишу все как надо.
Хотя разработчики возможно опять все переделают и возникнут другие насущные вопросы.
Всё, я ложусь, а лежачих, как говорится, не бьют.