предисловие
Недавно я прочитал новую документацию React16.3 и обнаружил, что официальная по-тихому изменила многое. Среди них меня больше всего интересует этот новый Context API. Поэтому я написал эту статью, чтобы обобщить и поделиться. Другие изменения могут быть упомянуты в этой статье.
Вы можете найти эту статью в моемgithubНайдено выше, пожалуйста, отметьте этот адрес для перепечатки.
Что такое контекстный API
Context APIпредоставляется React跨节点способ доступа к данным. Как мы все знаем, React — это односторонний поток данных.Vueвнутриpropsтакже позаимствовал эту идею.
Но часто настройка этого одностороннего потока данных становится не такой удобной. Нам часто нужно получить некоторые данные от узлов более высокого уровня, если мы используем традиционныеpropПередача данных требует, чтобы каждый слой передавался вручную. Для высокоуровневых компонентов этот метод очень раздражает и сильно снижает эффективность работы.
Итак, React используетContext API.Context APIстарый, но старыйContext APIПроблем много, да и пользоваться не особо удобно. Официалы не рекомендуют использовать старую версиюContext API. Многие разработчики выбираютReduxтакие как инструменты управления состоянием.
полученоReduxВлияние,ReactПредставленный в версии 16.3.0 совершенно новыйContext API.
Некоторые вещи, которые вам нужно знать заранее
-
Как мы все знаем, в JavaScript долгое время не было модульной системы.
nodejsиспользоватьrequireкак средство.ECMAScript6После этого новыйimportСтандарт грамматики.importСуществует особенно важное различие в грамматических стандартах (по сравнению сrequire),То есть:importНа импортированные данные ссылаются. Это означает, что несколько файлов, импортирующих одни и те же данные, являются не копиями, а ссылками. -
Файл объявления (d.ts) реакции @ 16.3, похоже, не обновляется, а это означает, что если вы сейчас используете Typescript, вы можете получить ошибку.
-
React теперь рекомендует использовать
render props,render propsЭто дает новую идею повторного использования кода и переноса кода рендеринга компонентов.propsПередайте функцию HOC для управления визуализацией компонента. -
Может быть, вы слышали "
Context APIиспользуется для заменыRedux', но это неправда.Reduxа такжеContext APIЗадачи, которые они решают, неодинаковы, и иллюзия может быть вызвана тем, что они используются немного одинаково. -
React16.3 имеет несколько новых функций, основное изменение — Context, а несколько жизненных циклов упразднены, например
ComponentWillReceiveProps(Честно говоря, в реальных проектах этот жизненный цикл можно использовать полностьюComponentWillUpdateзаменить) -
Ссылки в React 16.3 больше не рекомендуют передавать функцию напрямую, а вместо этого использовать новый
React.createRefзаменить. Конечно, предыдущий способ еще применим, все-таки он для совместимости.
начать использовать
React.createContext
createContextсоздатьContext, который принимает параметр, который будет использоваться какContextПередано значение по умолчанию. Следует отметить, что если параметр, который вы передаете, является объектом, то при измененииContextКогда внутренний звонокObject.isсравнивать объекты на равенство. Это может вызвать некоторые проблемы с производительностью. Конечно, это не имеет значения, потому что в большинстве случаев эта потеря производительности незначительна.
Давайте посмотрим на этот пример, который относится к типу темы (Светлая/Темная).Context.
// context.js
import * as React from 'react';
// 默认主题是Light
export const { Provider, Consumer } = React.createContext("Light");
Далее нам просто нужно добавить необходимые файлыimportпросто хорошо
Provider
Providerнеобходимо использоватьContextКорневой компонент всех компонентов . он принимаетvalueтак какprops, что значитContextпередать значение, оно изменит значение, которое вы создали вContextЗначение по умолчанию, установленное во время.
import { Provider } from './context';
import * as React from 'react';
import { render } from 'react-dom';
import App from './app';
const root = (
<Provider value='Dark'>
<App />
</Provider>
);
render(root, document.getElementById('root'));
Consumer
Consumerпредставляет потребителя, который принимаетrender propsкак единственныйchildren. По сути, это функция, эта функция получитContextДанные передаются как параметр, и компонент должен быть возвращен.
// app.jsx
import { Consumer } from './context';
import * as React from 'react';
export default class App extends React.Component {
render() {
return (
<Consumer>
{
theme => <div>Now, the theme is { theme }</div>
}
</Consumer>
)
}
}
Некоторые моменты, на которые следует обратить внимание
многоуровневая вложенность
ContextЧтобы обеспечить быстрый повторный рендеринг, React должен гарантировать, что каждыйConsumerявляются независимыми узлами.
const ThemeContext = React.createContext('light');
const UserContext = React.createContext();
function Toolbar(props) {
return (
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => (
<ProfilePage user={user} theme={theme} />
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
class App extends React.Component {
render() {
const {signedInUser, theme} = this.props;
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Toolbar />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
}
Это раздражает, когда слои более сложные. Поэтому рекомендуется, когда уровень превышает два уровня, создать свой собственныйrender propМожет быть, хорошая идея. В реальном проектировании многоуровневая вложенность не рекомендуется. более подходящим, предоставьте паруProvierа такжеConsumerДа, просто передайте экземпляр, соответствующий инструменту управления состоянием.
используется в жизненном цикле
в предыдущемContext API, в некоторых циклах объявления выставитcontextпараметры для облегчения доступа разработчиков. В новой версии API этот параметр не передается. Более рекомендуемым способом является передача напрямуюContextзначение черезpropsпередается компоненту. В частности, как этот официальный пример ниже.
class Button extends React.Component {
componentDidMount() {
// ThemeContext value is this.props.theme
}
componentDidUpdate(prevProps, prevState) {
// Previous ThemeContext value is prevProps.theme
// New ThemeContext value is this.props.theme
}
render() {
const {theme, children} = this.props;
return (
<button className={theme ? 'dark' : 'light'}>
{children}
</button>
);
}
}
export default props => (
<ThemeContext.Consumer>
{theme => <Button {...props} theme={theme} />}
</ThemeContext.Consumer>
);
В отличие от предыдущего, вы можете напрямую пройтиthis.contextдоступ, новая версияContextтолько вrenderспособ доступа. потому чтоContextподвергается толькоConsumerизrender propв. Лично я считаю это недостатком данной версии API. Таким образом, единственный способ использовать вышеуказанный компромисс — обернуть функциональный компонент, чтобы инкапсулировать его в свойства. Сравнительно говоря, это все еще немного хлопотно. В дереве компонентов есть еще один функциональный компонент, что также является недостатком.
Потребительский пакет
когдаContextКогда используется значение нескольких компонентов, вам нужно каждый раз вручную писатьConsumerа такжеredner prop. Это раздражает, а программисты ленивы (по крайней мере, я), поэтому на этот раз я использую HOC React, чтобы инкапсулировать его, чтобы упростить процесс.
const ThemeContext = React.createContext('light');
function ThemedButton(props) {
return (
<ThemeContext.Consumer>
{theme => <button className={theme} {...props} />}
</ThemeContext.Consumer>
);
}
Далее, когда вам нужно использоватьContextкогда не нужно ничего писатьConsumerохватывать
export default props => (
ThemeButton(props)
);
Форвардные рефери
когда вы инкапсулируетеConsumerПосле этого, возможно, вы захотите использовать ref для полученияConsumerЭкземпляр корневого компонента внутри или соответствующий DOM. если напрямуюConsumerИспользование ref на нем не даст желаемого результата. Итак, в React16.3 использовалась новая технология (не уверен, что она была представлена в 16.3), называемая转发refs. используется не только вContextВнутри, собственно, куда хочешь положитьrefПри переходе к подкомпонентам внутри компонента вы можете использовать转发refs.
В частности, вам нужно использовать новый API:React.forwardRef((props, ref) => React.ReactElement), возьмем в качестве примера следующее:
class FancyButton extends React.Component {}
// Use context to pass the current "theme" to FancyButton.
// Use forwardRef to pass refs to FancyButton as well.
export default React.forwardRef((props, ref) => (
<ThemeContext.Consumer>
{
theme => (
<FancyButton {...props} theme={theme} ref={ref} />
)
}
</ThemeContext.Consumer>
));
React.forwardRef()Принимает функцию в качестве параметра. На самом деле, вы можете думать об этой функции как о функциональном компоненте с тем же первым параметром, что и у функционального компонента. Отличие в том, что у него есть дополнительный реф. Это означает, что если выReact.forwardRefЕсли ref используется для созданного компонента, он не будет обрабатываться непосредственно компонентом, а будет перенаправлен внутрь для компонента, которому необходимо его переварить.
Если вам сложно понять, на самом деле этот метод можно заменить другим методом. Мы знаем, что в React ref не появляется в пропсах, он обрабатывается особым образом. Но почему бы просто не изменить название?
Следует отметить, что в прошлом мы получали ref путем передачи функции (string не рекомендуется, это проблема, оставшаяся от истории, ref в некоторых случаях не сможет получить правильное значение.vuejsможно использовать, не путайте). Но этот процесс очень напрягает, нужно только присвоить экземпляр или DOM соответствующей переменной, напрягает каждый раз писать один и тот же код шаблона. В «Тысяче звонков» React наконец слышит это. просто нужно сейчасReact.createRefЭто упрощает процесс.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
Это так просто в использовании, ничего особенного в этом нет.
Вернемся к теме выше, теперь мы используем реквизит для достижения转发refsфункция.
class Input extends React.Component {
reder() {
return (
<label>Autofocus Input:</label>
<input ref={this.props.forwardRef} type="text" />
)
}
}
function forwardRef(Component, ref) {
return (<Component forwardRef={ref} />);
}
// 使用forwardRef
let input = React.createRef();
forwardRef(Input, input);
// 当组件绑定成功之后
input.current.focus();
React.createRefВ возвращаемом значенииcurrentАтрибуты представляют соответствующий DOM или экземпляр компонента.forwardRefОсобого смысла нет, просто простой реквизит. Это использование похоже на状态提升Такой же.