Возвращаясь к React: контекст

React.js
Возвращаясь к React: контекст

предисловие

   Прежде всего, приглашаю всех подписаться на меняБлог на гитхабе, это можно расценивать как небольшое поощрение для меня.В конце концов, я не могу получить деньги, чтобы что-то написать.Я могу придерживаться своего собственного энтузиазма и всеобщего поощрения.Я надеюсь, что все обратят больше внимания! Я давно не писал React, и я обнаружил, что даже Контекст изменился.Внезапно я чувствую, что только что подключился к Интернету в деревне.Может быть, знания, упомянутые в статье, устарели, и это это только мой собственный опыт обучения.

Context

   Для разработчиков React Context должен быть знакомой концепцией, но до версии 16.3 React официально объявил ее устаревшей, утверждая, что эта функция является экспериментальным API и может быть удалена из более поздних версий. Но на практике многие сторонние библиотеки основаны на этой фиче, например: react-redux, mobx-react.

Как показано на дереве вышеуказанного компонента, существует множество компонентов между компонентом A и компонентом B. Если компонент a хочет пройти свойство для компонента B, вы должны использовать реквизиты для передачи свойства от компонента A через серию промежуточных компонентов к Окончательный компонент. Компонент B. Этот код не только очень неприятно, но, что более важно, промежуточные компоненты могут вообще не использовать это свойство, но должны предпринять ответственность за передачу, которую мы не хотим видеть. Цель контекста состоит в том, чтобы решить этот сценарий, чтобы мы могли непосредственно пропускать свойства от компонента до B компонента.

Legacy Context

   Упомянутая здесь старая версия Context относится к свойству Context, предоставленному в версии до React 16.3. На мой взгляд, этот Context используется согласованным образом. Поскольку поставщик свойств (Provider) должен явно объявить, к каким свойствам можно получить доступ через иерархию, и должен объявить тип этих свойств. Как пользователь свойства (Consumer) также необходимо явно объявить тип этих свойств. В официальной документации приведен следующий пример:

import React, {Component} from 'react';
import PropTypes from 'prop-types';

class Button extends React.Component {

    static contextTypes = {
        color: PropTypes.string
    };

    render() {
        return (
            <button style={{background: this.context.color}}>
                {this.props.children}
            </button>
        );
    }
}

class Message extends React.Component {
    render() {
        return (
            <div>
                {this.props.text} <Button>Delete</Button>
            </div>
        );
    }
}

class MessageList extends React.Component {
    static childContextTypes = {
        color: PropTypes.string
    };

    getChildContext() {
        return {color: "red"};
    }

    render() {
        const children = this.props.messages.map((message) =>
            <Message text={message.text} />
        );
        return <div>{children}</div>;
    }
}

Мы видим, чтоMessageListпо функцииgetChildContextЯвно заявить о предоставленииcolorсвойства и через статические свойстваchildContextTypesОн объявляет тип свойства. а такжеButtonчерез статические свойстваcontextTypesТип используемого атрибута объявляется, и они согласовывают передачу информации об атрибутах между уровнями. Контекст действительно очень удобен для решения ситуации с передачей атрибутов между слоями, но почему он официально не рекомендуется?

первыйContextИспользование React противоречит логике повторно используемых компонентов React.По мнению React, все компоненты должны иметь характеристики повторного использования, но именно из-за введения Context использование повторного использования компонентов стало строгим. Возьмите приведенный выше код в качестве примера, если вы хотите повторно использоватьButtonКомпоненты должны быть предоставлены в верхнем компонентеStringтип цветаContext, поэтому требования к повторному использованию ужесточаются. И что еще более важно, когда вы пытаетесь изменить значение контекста, может возникнуть неопределенное состояние. Возьмем пример, мы поместим вышеMessageListС небольшой модификацией содержимое контекста может быть изменено динамически:

class MessageList extends React.Component {

    state = {
        color: "red"
    };

    static childContextTypes = {
        color: PropTypes.string
    };

    getChildContext() {
        return {color: this.state.color};
    }

    render() {
        const children = this.props.messages.map((message) =>
            <Message text={message.text} />
        );
        return (
            <div>
                <div>{children}</div>
                <button onClick={this._changeColor}>Change Color</button>
            </div>
        );
    }

    _changeColor = () => {
        const colors = ["red", "green", "blue"];
        const index = (colors.indexOf(this.state.color) + 1) % 3;
        this.setState({
            color: colors[index]
        });
    }
}

   В приведенном выше примере мыMessageListКонтекст компонента предоставленcolorсвойство изменено наstateсвойство, когда каждое использованиеsetStateобновитьcolor, дочерние компоненты также будут обновлены, поэтому цвет соответствующей кнопки также изменится, и все выглядит идеально. Но как только между компонентами есть функция жизненного циклаShouldComponentUpdateТак что все становится странным. мы знаемPureComponentСуть в том, чтобы использоватьShouldComponentUpdateЧтобы избежать ненужных обновлений, мы можем внести небольшую модификацию в предыдущий пример:

class Message extends React.PureComponent {
    render() {
        return (
            <div>
                {this.props.text} <Button>Delete</Button>
            </div>
        );
    }
}

   Вы обнаружите, что даже если выMessageListизменилось вContextЗначение, не может привести к обновлению цвета кнопок узлов. Это потому чтоMessageКомпоненты наследуются отPureComponent, без получения новогоpropsизменить илиstateФункции жизненного цикла при измененииshouldComponentUpdateто, что возвращаетсяfalse,следовательноMessageи его подкомпоненты не обновляются, что приводит кButtonКомпоненты не обновляются до последнего цвета.

Если ваше значение контекста не изменяется, или используется только один раз, когда компонент инициализируется, то все проблемы не будут существовать. Но если вам нужно изменить контекст, как его использовать безопасно? Мишель ВестерстратHow to safely use React context В этой статье представлено решение внедрения зависимостей (DI). Автор считает, что мы не должны прямоgetChildContextСвойство состояния возвращается непосредственно в файле . Вместо этого conext следует использовать как внедрение зависимостей (DI).

class Theme {
    constructor(color) {
        this.color = color
        this.subscriptions = []
    }

    setColor(color) {
        this.color = color
        this.subscriptions.forEach(f => f())
    }

    subscribe(f) {
        this.subscriptions.push(f)
    }
}

class Button extends React.Component {
    static contextTypes = {
        theme: PropTypes.Object
    };

    componentDidMount() {
        this.context.theme.subscribe(() => this.forceUpdate());
    }

    render() {
        return (
            <button style={{background: this.context.theme.color}}>
                {this.props.children}
            </button>
        );
    }
}

class MessageList extends React.Component {

    constructor(props){
        super(props);
        this.theme = new Theme("red");
    }

    static childContextTypes = {
        theme: PropTypes.Object
    };

    getChildContext() {
        return {
            theme: this.theme
        };
    }

    render() {
        const children = this.props.messages.map((message) =>
            <Message text={message.text} />
        );
        return (
            <div>
                <div>{children}</div>
                <button onClick={this._changeColor}>Change Color</button>
            </div>
        );
    }

    _changeColor = () => {
        const colors = ["red", "green", "blue"];
        const index = (colors.indexOf(this.theme.color) + 1) % 3;
        this.theme.setColor(colors[index]);
    }
}

   В приведенном выше примере мы создалиThemeКласс, используемый для управления стилями, затемContextБудуThemeЭкземпляр передается вButtonПолучите экземпляр и подпишитесь на изменения стиля, вызовите, когда стиль изменитсяforceUpdateПринудительное обновление позволяет обновить интерфейс. Конечно, вышеприведенный пример — это всего лишь прототип, и при его использовании необходимо учитывать другие аспекты, такие как необходимость отмены мониторинга при уничтожении компонента.

   Оглядываясь назад на предыдущую версию Context, настроить ее довольно проблематично, особенно в соответствующих двух компонентах.childContextTypesа такжеcontextTypesОбъявите тип свойства контекста. И на самом деле объявления этих двух типов не очень хорошо ограничивают.context. В качестве примера предположим, что есть три компонента: Дедушка, Отец, Сын, и порядок рендеринга таков:

GrandFather -> Father -> Son

   Затем предположим, что контекст, предоставляемый компонентом GrandFather, имеет типnumberключvalueЗначение 1, в то время как предоставленный Отец также имеет типnumberключvalueЗначение 2, которое получает объявление компонента Son, имеет типnumberключvalueКонтекст, который мы знаем для определенных компонентов Сынаthis.context.valueЗначение равно 2, так как контекст должен брать ближайший родительский компонент, когда он встречает значение ключа с тем же именем.

   Точно так же мы предполагаем, что контекст, обеспечиваемый произведением GrandFather, относится к типуstringключvalueЗначение "1", в то время как отец предоставлен типnumberключvalueЗначение 2, которое получает объявление компонента Son, имеет типstringключvalueКОНТЕКСТ, то компонент СЫН примет значение КОНТЕКСТ дедушки? На самом деле его не будет, и значение, которое все равно берется, равно 2, но оно будет выводиться только в среде процесса разработки:

Invalid context value of type number supplied to Son, expected string

   Таким образом, мы можем вывести статические свойстваchildContextTypesа такжеcontextTypesОн может оказывать лишь вспомогательную роль для развития и не может играть обязывающую роль в актуальном значении контекста, но и в этом случае мы должны повторять ручной труд и декларировать его снова и снова.childContextTypesа такжеcontextTypesАтрибуты.

New Context

Новый Context был выпущен в React 16.3. По сравнению с предыдущим способом согласования и объявления компонентов, Context в новой версии сильно отличается.Он использует декларативный метод записи и получает Context через свойства рендеринга, которые не будут затронуты. по жизненному циклу.shouldComponentUpdateВлияние.上面的例子用新的Context改写为:

import React, {Component} from 'react';

const ThemeContext = React.createContext({ theme: 'red'});

class Button extends React.Component {
    render(){
        return(
            <ThemeContext.Consumer>
                {({color}) => {
                    return (
                        <button style={{background: color}}>
                            {this.props.children}
                        </button>
                    );
                }}
            </ThemeContext.Consumer>
        );
    }
}

class Message extends React.PureComponent {
    render() {
        return (
            <div>
                {this.props.text} <Button>Delete</Button>
            </div>
        );
    }
}

class MessageList extends React.Component {

    state = {
        theme: { color: "red" }
    };

    render() {
        return (
            <ThemeContext.Provider value={this.state.theme}>
                <div>
                    {this.props.messages.map((message) => <Message text={message.text}/>)}
                    <button onClick={this._changeColor}>Change Color</button>
                </div>
            </ThemeContext.Provider>
        )
    }

    _changeColor = () => {
        const colors = ["red", "green", "blue"];
        const index = (colors.indexOf(this.state.theme.color) + 1) % 3;
        this.setState({
            theme: {
                color: colors[index]
            }
        });
    }
}

   мы видим, что новый Context используетReact.createContextспособ создатьContextэкземпляр, а затем передатьProviderспособ предоставления значения контекста иConsumerЗначение контекста получается при взаимодействии с реквизитами рендеринга, даже если оно существует в промежуточном компоненте.shouldComponentUpdateвернутьfalse, это не приведет к невозможности обновления контекста и решит предыдущую проблему. мы видим это призваниеReact.createContextСоздайтеContextнапример, мы передаем значение по умолчаниюContextзначение, которое будет толькоConsumerНе удалось найти соответствие в дереве компонентовProviderиспользуется, поэтому даже если вы дадитеProviderизvalueвходящийundefinedстоимость,ConsumerТакже не будет использоваться значение по умолчанию.

  Новая версия Context API больше соответствует идее React, чем предыдущая версия Context API, и может решить проблемуcomponentShouldUpdateпоставленные проблемы. В то же время в ваш проект необходимо добавить специальные файлы для созданияContext. В React v17 поддержка старого Context API может быть удалена, поэтому его все равно необходимо обновить как можно скорее. Так много было сказано в конце, но мы должны стараться избегать злоупотреблений Context в проекте, иначе зависимости между компонентами будут слишком сложными.