Comments 50
- Вместо componentWillMount — для сайд-эффектов более оптимальным вариантом будет componentDidMount, а для работы со state и props можно использовать конструктор
- Вместо componentWillUpdate — для сайд-эффектов предлагается componentDidUpdate, а для работы со state нам дают getDerivedStateFromProps
Объединение componentWillMount и componentWillReceiveProps под одним getDerivedStateFromProps имеет смысл, что бы убрать дублирование кода.
Если говорить без привязки к фреймворкам наличие у компонента
— willGetParams
— shouldChange?
— willMount/willUpdate
****** render
— didUpdate
— willUnmount
P.S. вобщем у меня смешаные чувства по поводу грядущего обновления, и мне кажется что он принесет больше пробем чем пользы.
А для чего вы ими пользовались?
Для большинства операций больше пододят did-хуки, то есть componentDidMount и componentDidUpdate. Если нужно предотвратить обновление, то есть shouldComponentUpdate.
will-версии и так не рекомендовались к использованию.
Не кисло так за 4 года 16 версия, они хоть немного совместимы между собой?
Суть в том что теперь есть обёртка в виде Consumer. А данные из контекста передаются дальше в виде props.
Что-то мешает взять пропсы и передать дочернему компоненту?
Изобретаем HOC? Мешает то, что получаем ещё 1 уровень вложенности, который усложняет и debugging, и несущественно, но всё же влияет на производительность. У нас и так в сложном приложении повсюду возможны матрёшки из HOC, а так матрёшками нужно покрывать компоненты, которые используют context. Добавить к этому какие-нибудь styled-components, где матрёшкой обёрнут каждый тег со стилями, и получается, что древо компонент почти целиком состоит из композитных матрёшек. С точки зрения приложения — ок, работает же. С остальных точек зрения — спорно, очень спорно.
А я про inject
в mobx-react
и connect
в redux-react
и не заикался. Это изначально сильно impure
-вещи, которые идут как большой компромисс между идеализмом и практикой. Я скорее про тот context
, что разработчики по своей воле сами используют в сложных проектах, устав от props-hell
. Ниже отписал подробнее.
Изобретаем HOC? Мешает то, что получаем ещё 1 уровень вложенности, который усложняет и debugging, и несущественно, но всё же влияет на производительность. У нас и так в сложном приложении повсюду возможны матрёшки из HOC, а так матрёшками нужно покрывать компоненты, которые используют context. Добавить к этому какие-нибудь styled-components, где матрёшкой обёрнут каждый тег со стилями, и получается, что древо компонент почти целиком состоит из композитных матрёшек. С точки зрения приложения — ок, работает же. С остальных точек зрения — спорно, очень спорно..
Используйте для HOC наследование вместо композиции и никаких новых уровней вложенности добавляться не будет
Вы знаете, я бы с радостью. Но вот не совсем понимаю, как его тут применить. Судя по тем обрывкам, что я увидел касательно нового API, он работает именно в рамках .render
-метода. Ну и по сути асинхронно. Не подскажите рецепт? В голову приходят только разного рода извращения, работающие с задержкой.
Да просто использовать те же хокки но только не в виде
function hoc(TargetCmponent){
return class extends React.Component {
render(){
return <TargetCmponent {...this.props}/>
}
}
}
а в виде
function hoc(target){
const isTargetClass = target instanceof React.Component
return class extends (isTargetClass ? target : React.Component) {
render(){
return isTargetClass ? super.render() : target(this.props)
}
}
}
и все, больше никаких вложенностей и глубоких матрешек из компонентов-оберток — сколько бы хокков мы бы не навесили на компонент — да хоть тысячу — в рантайме все равно будет создан только один компонент что будет быстрее, так как вызов рендера не создаст тысячу объектов-элементов виртуального дума для этих врапперов, и реакту нужно будет выполнить diff одного компонента а не тысячи
Вы, вероятно, имели ввиду примерно это:
function hoc(Target, key)
{
const isTargetClass = target instanceof React.Component;
const Parent = isTargetClass ? Target : React.Component;
return class extends Parent
{
render()
{
return <Consumer>{val =>
{
const props = {...this.props, [key]: val };
return isTargetClass
? super.render(props)
: target(props);
}}</Consumer>
}
};
}
Однако в данном случае мы располагаем нужным нам context-полем только внутри super.render
. А сложные stateFull компоненты хотят его в других своих методах, включая, возможно, всякие хуки. В теории можно и это обойти, к примеру, задействовав setState
. Но тогда мы должны писать компонент из рассчёта на двойной render
при инициализации, и, возможно, при одновременной смене чего-нибудь в props
и в context
(но это уже редкий use case).
Идём дальше. Что если нам нужно несколько полей, а не одно? Нужно городить уже достаточно сложную обвязку. Всё выполнимо, но от этого кода сильно несёт… каким-то альтернативным react-ом.
А вообще, если нужно связывать несколько полей, то зачем нужен контекст — не лучше ли использовать внешнее состояние?
Вы имеете внешнее хранилище? Ну вот я использую redux. Но там данные приложения. А вещи которые я пробрасываю через context "руками" другого характера. Сложно объяснить. Ну кроме одной — i18n, по сути переводы и инструментарий к ним.
Да можно написать свой внешний context, по принципу того же redux-react
-а. Но это что-то вроде из стрельбы пушки по воробьям. Серьёзное архитектурное решение.
Старый context при всей своей уродливости был в чём-то изящен. Идеологически. Предполагалось, что туда помещают либо совсем что-то impure, и тогда сами ковыряйтесь в этом как хотите (react-redux скажем), либо что-то статическое, и тогда вся машинерия по re-render-у уже не нужна, а вот от килотонн бойлерплейта избавляет.
А новый контекст гораздо больше напоминает прицельные порталы для изменяемых значений. Теперь наследник окольными путями (скажем через import-export) узнаёт что-то от родителя (react в этом совсем не помогает), и позволяет от этого асинхронным образом в render методе зависеть. Вероятно, это выглядит достаточно изящно, при решении каких-то задач. Не знаю.
По поводу внешнего хранилища. Тут ещё встаёт вопрос реализации. Скажем redux-react очень уязвим в вопросе количества подписчиков. Скажем если у вас приложение рендерит сотни или тысячи мелких компонент, то подписываясь в каждом из них на store, вы заставляете connect вычислять mapStateToProps на любой чих (коих может быть очень много). В лучшем случае это просто будет жрать батарейку, в худшем ещё и тормозить.
Ну контекст это по сути просто объект в который помечаются общие для какого-то поддерева компонентов данные. Если данные контекста не зависят от того в каком поддереве находится компонент например i18n и другие данные то мы можем просто вынести этот контекст во внешний объект в отдельном файле выполнить заимпортить этот объект и обращаться к нему напрямую. Если контекст у разных поддеревьях компонентов разный то всегда можно выразить эту сутуацию не в через компоненты а через данные.
По поводу внешнего хранилища. Тут ещё встаёт вопрос реализации. Скажем redux-react очень уязвим в вопросе количества подписчиков. Скажем если у вас приложение рендерит сотни или тысячи мелких компонент, то подписываясь в каждом из них на store, вы заставляете connect вычислять mapStateToProps на любой чих (коих может быть очень много). В лучшем случае это просто будет жрать батарейку, в худшем ещё и тормозить.
O, вы обратились по адресу — в статьях тут, тут и тут я разбирал эту проблему и ее решение в виде mobx
А старая концепция context-а, предполагала, что в нём лежат статичные данные, нечто вроде констант, которые не требуется передавать явным образом. Естественно я не имею ввиду redux и прочие динамичные штуки, а скорее какой-нибудь i18n и ему подобные вещи. Это касается в первую очередь сложных приложений, где такой подход работы с контекстном позволяет избежать много-много-многочисленных пробрасываний каких-то обобщённых props по всему большой вложенности древу вниз.
Новый же context, если я правильно понял, вполне себе динамический. Что-то вроде портала для отдельно взятых значений. Которые при этом не зазорно и менять в родительских render-ах. Получившаяся штука выходит довольно могучей, но решает она, явно какие-то другие задачи.
Ок, допустим. На самом верхнем уровне, где лежит какой-нибудь <i18n-provider/>
можно просто в случае "динамики" (например смены языка) проставить новый key
и, хотя мы разово перетрясём всё vdom-древо, мы добьёмся нужных результатов. При том, что на кнопку хорошо если хоть кто-нибудь нажмёт. Мне такой подход показался разумным компромиссом. А теперь же для обеспечения того же функционала, нужно (в моих проектах) покрывать HOC каждый 3-й компонент. Contex позволял обойтись малыми жертвами. А случае использования наследования давался практически бесплатно.
Хм. Отдельный момент эти сами .consumer
-ы. Если я ничего не напутал, то их нужно передавать по ссылке. Т.е. самым явным образом. Скажем при помощи import-export-а
, а это, в свою очередь, усложняет жизнь, не просто лишним бойлерплейтом, а ещё и тем, что если у нас один parent-component, мог порождать несколько вариаций context-значений, исходя из условий, то теперь надо будет как-то самостоятельно разграничивать их своими силами, и react
нам в этом больше не помощник.
Нет, я конечно ожидал, что context
пересмотрят, но чтобы настолько… необычно. Этого вот я не ждал. Мне даже сложно назвать это контекстом. Скорее порталом :)
Что-то мешает взять пропсы и передать дочернему компоненту?
Вы понимаете зачем нужен контекст? Чтобы не пользоваться пропсами, для того, чтобы гонять по дочерним компонентам всякие вещи типа сторов. По сути, это единственный способ нормального DI в Реакте.
А вот вы мне предлагаете, если я хочу воспользоваться контекстом, чтобы не использовать пропсы — использовать пропсы? Тогда зачем вообще нужен такой костыльный контекст, если нужно просто передавать во все компоненты в пропсы еще и контекст вручную?
А что случилось-то? Ну делал он раньше var additionalProps = grabStoresFn(this.context.mobxStores || {}, newProps, this.context) || {}
, а теперь будет делать
<MobxStores.Consumer>
{(mobxStores) => {
var additionalProps = grabStoresFn(mobxStores, newProps)
// ...
return createElement(component, newProps)
}}
</MobxStores.Consumer>
Или вы про то, что третий аргумент неоткуда взять теперь? А его вообще кто-то использует? Всегда же он был в состоянии "недодокументирован"...
В таком случае я не понимаю как вы использовали inject до этого… Документация говорит что его надо использовать вот так:
@inject("color") @observer
class Button extends React.Component {
render() {
return (
<button style={{background: this.props.color}}>
{this.props.children}
</button>
);
}
}
То есть зависимости попадают в props, откуда их можно достать в любом методе.
То есть я могу использовать его так:
@inject("counter") @observer
class MyComponent extends Component<{ id: number, counter: CounterStore }> {
onButtonClick() {
const { id, counter } = this.props;
counter.get(id).increaseAction();
}
render() {
const { id, counter } = this.props;
return (
<CoolButton onClick={this.onButtonClick}>
{counter.get(id).value}
</CoolButton>
);
}
}
Видите? В любом методе класса, а не только в части метода рендер.
Что поменялось-то?
PS Вы вообще в исходники mobx-react заглядывали? Посмотрите как внутри inject устроен: github.com/mobxjs/mobx-react/blob/master/src/inject.js#L45
Туда Consumer идеально зайдет…
До this.state
там дотягиваться и не нужно, т.к. он передаётся как параметр метода. А вот до всех остальных полей компонента уже по-нормальному никак. Довольно странное решение, если учесть, в документации открытым текстом сказано, что не стоит помещать в .state
поле ничего, что не участвует в .render
, мол помещайте "как есть" в this.%property%
. Поместили. А теперь туда доступа нет :)
Как сравнить this.props и nextProps? Вернее, где теперь сравнить props, пришедшие, например, из redux??!
Кстати да. В большинстве не сильно хитрых случаев componentDidUpdate
вполне сгодится. Правда вот порылся в своём коде и увидел что у меня есть методы componentWillReceiveProps
, которые оперируют offsetWidth
и offsetHeight
от <SVG/>
для формирования нового state
. Теперь чтобы иметь к ним доступ нужно этот DOMElement
толкать в state
. В раздумьях. Насколько здравая идея толкать в .state
DOM-элементы, а не данные. Логика подсказывает, что ничего криминального.
Интересный момент есть, связанный с <Provider/>
-ми. Представьте, что вы maintainer redux-а. И вам нужно переехать на новый API
. Вы, естественно не хотите, чтобы пользователи из-за обновления стали переписывать все свои container
-а. Но что делать? Ведь если мы будем порождать новый Context.{Provider|Consumer}
для каждого store
-а, а внутри реализации connect
-обёртки мы не будем иметь доступ к соответствующему <Consumer/>
, и всё накроется медным тазом. Нужно будет пробрасывать store
повсеместно. Т.е. переписывать все контейнера. Да и переиспользуемость компонент накроется медным тазом тоже. Они будут явным образом зашиты на один конкретный store
.
Оказалось, что всё намного проще. redux-react
может позволить себе один единственный <Provider/>
на всю свою библиотеку, сколько бы store
-ов у вас не было. И createContext
будет вызван именно внутри библиотеки. Всё дело в том, что один и тот же <Provider/>
можно вкладывать в себя же самого сколько душе угодно, и всякий раз при этом менять значение. А каждый <Consumer/>
будет иметь доступ только к тому значению, которое ближайшее к нему по древу.
Т.е. при первоначальном взгляде на <Provider/>
он напоминает телепорт значения сверху внизу, минуя props
. На деле же каждый ReactElement
порождённый <Provider/>
-классом является самостоятельной сущностью со своим собственным значением. Указав во внутреннем <Provider/>
-е другое значение, мы значение верхнего не изменим, и вообще ничего не сломается. Они не будут иметь с друг другом ничего общего. Хотя ссылки на <Provider/>
-ы и <Cosumer/>
-ы будут ===
.
Мне нет. Даже в голову не пришло. Более того я уже столько материалов на эту тему нарыл, столько перечитал. Перерыл кучу обсуждений на github-е, RFC посмотрел и все доводы за и против, мотивы решения. Даже стал песочницу собирать. А потом случайно в комментариях на Medium наткнулся на вопрос про вложенность и даже ссылку на рабочий sandbox. И только поковыряв его до меня дошло, как на деле я буду его использовать (несколько вложенных instance от одного и того же Provider-а в одном древе).
Новшества React 16.3(.0-alpha)