предисловие
Context
В контексте контекста эта концепция часто встречается в мире программирования, а также присутствует в React.
В официальной документации React,Context
Он классифицируется как Advanced и относится к расширенному API React, но официально не рекомендуется использовать Context в стабильной версии приложения.
The vast majority of applications do not need to use content.
If you want your application to be stable, don't use context. It is an experimental API and it is likely to break in future releases of React.
Однако это не означает, что нам не нужно обращать вниманиеContext
. Фактически, многие превосходные компоненты React дополняют свои собственные функции через контекст, например React-Redux.<Provider />
, то есть черезContext
обеспечить глобальныйstore
, перетащите компонент react-dnd черезContext
Распределите события перетаскивания DOM в компоненте и направьте компонентный реактивный маршрутизатор черезContext
Управление состоянием маршрутизации и многое другое. В разработке компонента React, если вы используете его правильноContext
, может сделать ваши компоненты мощными и гибкими.
Я просто хочу поговорить с вами сегодня, что я узнал во время разработкиContext
и как я использую его для разработки компонентов.
Примечание. Все приложения, упомянутые в этой статье, относятся к веб-приложениям.
Знакомство с контекстом React
Официальное определение контекста
Официальный сайт документации React неверенContext
Дает определение «что есть» и является скорее описанием использованияContext
сценарии и как использоватьContext
.
Официальный сайт для использованияContext
Сценарий описывается так:
In Some Cases, you want to pass data through the component tree without having to pass the props down manuallys at every level. you can do this directly in React with the powerful "context" API.
Проще говоря, когда вы не хотите проходить слой за слоем в дереве компонентовprops
илиstate
способ пройти данные, вы можете использоватьContext
реализоватьпо уровнямпередача данных компонента.
Используйте реквизиты или состояние для передачи данных, и данные передаются сверху вниз.
использоватьContext
, который может передавать данные между компонентами.
Как использовать контекст
Если вы хотитеContext
Для работы необходимы два компонента, одинContext
Производитель (Provider), обычно родительский узел, и еще одинContext
Потребитель (Consumer), обычно один или несколько дочерних узлов. такContext
использовать на основеМодель «производитель-потребитель».
Для родительского компонента этоContext
Производитель, необходимо передать статическое свойствоchildContextTypes
Объявить предоставленные дочерним компонентамContext
свойства объекта и реализует экземплярgetChildContext
метод, который возвращает делегатContext
Простой объект (простой объект).
import React from 'react'
import PropTypes from 'prop-types'
class MiddleComponent extends React.Component {
render () {
return <ChildComponent />
}
}
class ParentComponent extends React.Component {
// 声明Context对象属性
static childContextTypes = {
propA: PropTypes.string,
methodA: PropTypes.func
}
// 返回Context对象,方法名是约定好的
getChildContext () {
return {
propA: 'propA',
methodA: () => 'methodA'
}
}
render () {
return <MiddleComponent />
}
}
И дляContext
Потребитель получает доступ к родительскому компоненту следующим образом.Context
.
import React from 'react'
import PropTypes from 'prop-types'
class ChildComponent extends React.Component {
// 声明需要使用的Context属性
static contextTypes = {
propA: PropTypes.string
}
render () {
const {
propA,
methodA
} = this.context
console.log(`context.propA = ${propA}`) // context.propA = propA
console.log(`context.methodA = ${methodA}`) // context.methodA = undefined
return ...
}
}
Дочерние компоненты должны передавать статическое свойствоcontextTypes
После объявления можно получить доступ к родительскому компонентуContext
Атрибуты объекта, в противном случае, даже если имя атрибута правильное, полученный объектundefined
.
Для сокращения без исключения (компонент без гражданства) можно получить доступ к родительскому компонентуContext
import React from 'react'
import PropTypes from 'prop-types'
const ChildComponent = (props, context) => {
const {
propA
} = context
console.log(`context.propA = ${propA}`) // context.propA = propA
return ...
}
ChildComponent.contextProps = {
propA: PropTypes.string
}
В следующем выпуске React будетContext
API был скорректирован, чтобы сделать более понятным, как используется режим производитель-потребитель.
import React from 'react';
import ReactDOM from 'react-dom';
const ThemeContext = React.createContext({
background: 'red',
color: 'white'
});
через статический методReact.createContext()
СоздаватьContext
объект, этоContext
Объект содержит два компонента,<Provider />
а также<Consumer />
.
class App extends React.Component {
render () {
return (
<ThemeContext.Provider value={{background: 'green', color: 'white'}}>
<Header />
</ThemeContext.Provider>
);
}
}
<Provider />
изvalue
эквивалентно настоящемуgetChildContext()
.
class Header extends React.Component {
render () {
return (
<Title>Hello React Context API</Title>
);
}
}
class Title extends React.Component {
render () {
return (
<ThemeContext.Consumer>
{context => (
<h1 style={{background: context.background, color: context.color}}>
{this.props.children}
</h1>
)}
</ThemeContext.Consumer>
);
}
}
<Consumer />
изchildren
Должна быть функция, полученная через параметры функции<Provider />
который предоставилContext
.
видимый,Context
Новый API ближе к стилю React.
Несколько мест, где контекст можно получить напрямую
На самом деле, в дополнение к экземпляруcontext
Атрибуты(this.context
), есть много мест, где компоненты React могут напрямую обращаться кContext
. Например, метод построения:
constructor(props, context)
Например, жизненный цикл:
componentWillReceiveProps(nextProps, nextContext)
shouldComponentUpdate(nextProps, nextState, nextContext)
componetWillUpdate(nextProps, nextState, nextContext)
Для функционально-ориентированных компонентов без сохранения состояния вы можете напрямую обращаться кContext
.
const StatelessComponent = (props, context) => (
......
)
ВышеContext
Основу, более конкретное содержание руководства можно найти вздесь
Мое понимание контекста
Хорошо, после основ, давайте поговорим о моем ReactContext
понимание.
Рассматривать контекст как область действия компонента
Разработчики, использующие React, знают, что приложение React — это, по сути, дерево компонентов React.Каждый компонент React эквивалентен узлу в этом дереве.За исключением корневого узла приложения, каждый другой узел имеет цепочку родительских компонентов.
Например, картинка выше,<Child />
Цепочка родительских компонентов<SubNode />
-- <Node />
-- <App />
,<SubNode />
Цепочка родительских компонентов<Node />
-- <App />
,<Node />
Цепочка родительских компонентов имеет только один компонентный узел, который<App />
.
Эти соединенные деревом узлы компонентов фактически образуютContext
дерево, каждый узелContext
, от всех узлов компонентов в цепочке родительских компонентов черезgetChildContext()
При условииContext
Объект, состоящий из объектов.
Разработчики, понимающие концепцию цепочки областей действия JS, должны знать, что во время выполнения блока кода JS будет создана соответствующая цепочка областей действия, в которой записываются активные объекты, к которым можно получить доступ во время выполнения блока кода JS во время выполнения. ., включая переменные и функции, программы JS получают доступ к переменным и функциям внутри или вне блока кода через цепочку областей видимости.
Если в качестве аналогии используется цепочка областей видимости JS, компоненты React обеспечиваютContext
Объект на самом деле похож на область, доступную для дочерних компонентов, иContext
Свойства объекта можно рассматривать как активные объекты в области видимости. Из-за компонентаContext
Передано всеми компонентами в родительской цепочкеgetChildContext()
вернутьContext
Объекты составлены, поэтому компоненты проходят черезContext
Он предоставляется всеми компонентами узла в цепочке родительских компонентов, к которым можно получить доступ.Context
характеристики.
Итак, я позаимствовал идею JS scope chain, поставилContext
как будтообъем компонентаиспользовать.
Сосредоточьтесь на управляемости и объеме контекста.
Однако, рассматриваемый как область действия компонентаContext
Это отличается от общепринятой концепции области действия (в языках программирования, с которыми я сейчас сталкиваюсь). нам нужно обратить вниманиеContext
управляемость и радиус действия.
В нашей обычной разработке использование области действия или контекста очень распространено, естественно и даже незаметно.Context
Это не так просто. родительский компонент предоставляетContext
нужно пройтиchildContextTypes
Сделайте «декларацию», дочерний компонент использует родительский компонентContext
свойства должны пройтиcontextTypes
сделать «применить», поэтому я думаю, что ReactContext
Является "разрешенной" областью действия компонента.
Каковы преимущества этого «разрешенного» подхода? Насколько я понимаю, во-первых, необходимо поддерживать согласованность API фреймворка, аpropTypes
Опять же, используйте декларативный стиль кодирования. Кроме того,В определенной степени можно гарантировать, что компоненты, предоставляемыеContext
управляемость и досягаемость.
Компоненты приложения React представляют собой древовидные структуры, расширяющиеся слой за слоем, а компоненты «родитель-потомок» представляют собой линейные зависимости «один ко многим». использовать по желаниюContext
На самом деле эта зависимость будет уничтожена, что приведет к некоторым ненужным дополнительным зависимостям между компонентами, что уменьшит возможность повторного использования компонентов, что может повлиять на ремонтопригодность приложения.
Как видно из приведенного выше рисунка, дерево компонентов, которое изначально было линейно зависимым, поскольку дочерний компонент использует родительский компонентContext
, Привести к<Child />
пара компонентов<Node />
а также<App />
У всех есть зависимости. После отделения от этих двух компонентов<Child />
Наличие<Child />
возможности повторного использования.
я считаю,пройти черезContext
Предоставление данных или API не является элегантной практикой., несмотря на то, что делает react-redux. Следовательно, необходим механизм или ограничение для уменьшения ненужных эффектов.
пройти черезchildContextTypes
а такжеcontextTypes
Ограничения этих двух статических свойств могут быть гарантированы в определенной степени.Только сам компонент или другие подкомпоненты, связанные с компонентом, могут быть доступны по желанию.Context
свойства, либо данные, либо функции. Потому что только сам компонент или связанные с ним подкомпоненты могут знать, что он может получить доступContext
Какие свойства относительно других компонентов, которые не связаны с компонентом, будь то внутренние или внешние, потому что неясно о родительских компонентах в цепочке родительских компонентовchildContextTypes
Что "заявлено"Context
атрибут, поэтому он не может пройтиcontextTypes
«Применить» связанные свойства. Итак, я понимаю, что область действия, заданная компонентуContext
"С разрешения" можно в какой-то мере обеспечитьContext
управляемость и радиус действия.
В процессе разработки компонентов мы всегда должны обращать на это внимание, а не использовать это по своему усмотрению.Context
.
Не нужно сначала использовать Context
Поскольку высокоуровневый API React, React иНе рекомендуется, мы отдаем приоритет использованиюContext
. Мое понимание:
-
Context
Он все еще находится в экспериментальной стадии, и в более поздних версиях релиза могут быть серьезные изменения.На самом деле такая ситуация уже произошла.Поэтому, чтобы избежать большего влияния и проблем при будущих обновлениях, не рекомендуется его использовать в приложении.Context
. - Хотя это не рекомендуется для использования в приложениях
Context
, но для компонентов, поскольку сфера влияния меньше, чем у приложения, если вы можете достичь высокой связности и не разрушить зависимости дерева компонентов, вы все равно можете рассмотреть возможность использованияContext
из. - Для обмена данными или управления состоянием между компонентами предпочтительно использовать
props
илиstate
Решите, а затем рассмотрите возможность использования других сторонних зрелых библиотек для решения, когда вышеуказанные методы не являются лучшим выбором, рассмотрите возможность использованияContext
. -
Context
Обновление должно пройтиsetState()
триггер, но это ненадежно.Context
Поддержка кросс-сборок, однако, если промежуточные подкомпоненты не обновляются некоторыми методами, такими какshouldComponentUpdate()
вернутьfalse
, то нет гарантииContext
Обновление должно быть доступно для использованияContext
подкомпонент . следовательно,Context
надежность требует внимания. Однако обновленная проблема была решена в новой версии API.
Короче говоря, пока вы можете обеспечитьContext
можно контролировать, используяContext
Серьезной проблемы нет, даже если ее можно разумно применить,Context
На самом деле, это может дать очень мощный опыт в разработке компонентов React.
Используйте Context как средство для обмена данными
Официально упоминаетсяContext
Может использоваться для передачи данных между компонентами. И я, понимаю, как мост, как средаобмен данными. Обмен данными можно разделить на две категории:Уровень приложенияа такжекомпонентный уровень.
- Обмен данными на уровне приложения
Предоставляется компонентом корневого узла приложения.Context
Объект можно рассматривать как глобальную область действия уровня приложения, поэтому мы используем компонент корневого узла приложения, чтобы предоставитьContext
Объект создает некоторые глобальные данные на уровне приложения. Готовые примеры могут относиться к react-redux, ниже приведены<Provider />
Основная реализация исходного кода компонента:
export function createProvider(storeKey = 'store', subKey) {
const subscriptionKey = subKey || `${storeKey}Subscription`
class Provider extends Component {
getChildContext() {
return { [storeKey]: this[storeKey], [subscriptionKey]: null }
}
constructor(props, context) {
super(props, context)
this[storeKey] = props.store;
}
render() {
return Children.only(this.props.children)
}
}
// ......
Provider.propTypes = {
store: storeShape.isRequired,
children: PropTypes.element.isRequired,
}
Provider.childContextTypes = {
[storeKey]: storeShape.isRequired,
[subscriptionKey]: subscriptionShape,
}
return Provider
}
export default createProvider()
Корневой компонент приложения<Provider />
После того, как компонент упакован, он, по сути, предоставляет глобальное свойство для приложения.store
, что эквивалентно совместному использованию во всем приложенииstore
Атрибуты. Конечно,<Provider />
Компоненты также могут быть заключены в другие компоненты и совместно использоваться глобально на уровне компонентов.store
.
- Обмен данными на уровне компонентов
Если функция компонента не может быть выполнена самим компонентом, но также необходимо полагаться на дополнительные подкомпоненты, то вы можете использоватьContext
Создайте компонент, состоящий из нескольких подкомпонентов. Например, реактивный маршрутизатор.
реактивный маршрутизатор<Router />
Он не может выполнять операции и управление маршрутизацией самостоятельно, поскольку содержание навигационных ссылок и переходов обычно разнесено, поэтому ему также необходимо полагаться на<Link />
а также<Route />
Осадочные компоненты для завершения сопутствующих работ по маршруту. Чтобы соответствующие подкомпоненты работали вместе, используется реализация React-Router.Context
существует<Router />
,<Link />
так же как<Route />
Совместно между этими связанными компонентамиrouter
, а затем завершите унифицированную работу и управление маршрутизацией.
Взято ниже<Router />
,<Link />
так же как<Route />
Эти связанные компоненты являются частью исходного кода для лучшего понимания вышеизложенного.
// Router.js
/**
* The public API for putting history on context.
*/
class Router extends React.Component {
static propTypes = {
history: PropTypes.object.isRequired,
children: PropTypes.node
};
static contextTypes = {
router: PropTypes.object
};
static childContextTypes = {
router: PropTypes.object.isRequired
};
getChildContext() {
return {
router: {
...this.context.router,
history: this.props.history,
route: {
location: this.props.history.location,
match: this.state.match
}
}
};
}
// ......
componentWillMount() {
const { children, history } = this.props;
// ......
this.unlisten = history.listen(() => {
this.setState({
match: this.computeMatch(history.location.pathname)
});
});
}
// ......
}
Хотя исходный код имеет другую логику, но<Router />
Суть в том, чтобы предоставить дочернему компонентуrouter
атрибутContext
, при мониторингеhistory
,однаждыhistory
изменение, черезsetState()
Запустите компонент для повторного рендеринга.
// Link.js
/**
* The public API for rendering a history-aware <a>.
*/
class Link extends React.Component {
// ......
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.shape({
push: PropTypes.func.isRequired,
replace: PropTypes.func.isRequired,
createHref: PropTypes.func.isRequired
}).isRequired
}).isRequired
};
handleClick = event => {
if (this.props.onClick) this.props.onClick(event);
if (
!event.defaultPrevented &&
event.button === 0 &&
!this.props.target &&
!isModifiedEvent(event)
) {
event.preventDefault();
// 使用<Router />组件提供的router实例
const { history } = this.context.router;
const { replace, to } = this.props;
if (replace) {
history.replace(to);
} else {
history.push(to);
}
}
};
render() {
const { replace, to, innerRef, ...props } = this.props;
// ...
const { history } = this.context.router;
const location =
typeof to === "string"
? createLocation(to, null, null, history.location)
: to;
const href = history.createHref(location);
return (
<a {...props} onClick={this.handleClick} href={href} ref={innerRef} />
);
}
}
<Link />
Ядро рендеринга<a>
метка, перехват<a>
событие щелчка метки, а затем передать<Router />
общийrouter
правильноhistory
Выполнение операций маршрутизации для уведомления<Router />
Перерендерить.
// Route.js
/**
* The public API for matching a single path and rendering.
*/
class Route extends React.Component {
// ......
state = {
match: this.computeMatch(this.props, this.context.router)
};
// 计算匹配的路径,匹配的话,会返回一个匹配对象,否则返回null
computeMatch(
{ computedMatch, location, path, strict, exact, sensitive },
router
) {
if (computedMatch) return computedMatch;
// ......
const { route } = router;
const pathname = (location || route.location).pathname;
return matchPath(pathname, { path, strict, exact, sensitive }, route.match);
}
// ......
render() {
const { match } = this.state;
const { children, component, render } = this.props;
const { history, route, staticContext } = this.context.router;
const location = this.props.location || route.location;
const props = { match, location, history, staticContext };
if (component) return match ? React.createElement(component, props) : null;
if (render) return match ? render(props) : null;
if (typeof children === "function") return children(props);
if (children && !isEmptyChildren(children))
return React.Children.only(children);
return null;
}
}
<Route />
Есть часть исходного кода с<Router />
Аналогично, вложенность маршрутов может быть достигнута, но суть в том, чтобы пройтиContext
общийrouter
, определите, соответствует ли он пути текущего маршрута, а затем визуализируйте компонент.
Из приведенного выше анализа видно, что весь реагирующий маршрутизатор на самом деле вращается вокруг<Router />
изContext
строить.
Используйте Context для разработки компонентов
раньше, черезContext
Разработал простой компонент — компонент распределения слотов. В этой главе мы поговорим о том, как использовать опыт разработки этого компонента распределения слотов.Context
Разрабатывать компоненты.
компоненты распределения слотов
Прежде всего, что такое компонент диспетчеризации слотов, эта концепция впервые была признана в Vuejs. Распределение слотов — это технология, которая вставляет содержимое родительского компонента в шаблон дочернего компонента через композицию компонентов, которая вызывается в Vuejs.Slot
.
Чтобы все могли понять эту концепцию более интуитивно, я привёл демонстрацию о распределении слотов от Vuejs.
компоненты для предусмотренных слотов<my-component />
, шаблон выглядит следующим образом:
<div>
<h2>我是子组件的标题</h2>
<slot>
只有在没有要分发的内容时显示
</slot>
</div>
Для родительского компонента шаблон выглядит следующим образом:
<div>
<h1>我是父组件的标题</h1>
<my-component>
<p>这是一些初始内容</p>
<p>这是更多的初始内容</p>
</my-component>
</div>
Окончательный результат визуализации:
<div>
<h1>我是父组件的标题</h1>
<div>
<h2>我是子组件的标题</h2>
<p>这是一些初始内容</p>
<p>这是更多的初始内容</p>
</div>
</div>
компоненты можно увидеть<my-component />
из<slot />
Узел в конечном итоге помещается в родительский компонент<my-component />
Заменено содержимым под узлом.
Vuejs также поддерживаетименованный слот.
Например, компонент макета<app-layout />
:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
И в шаблоне родительского компонента:
<app-layout>
<h1 slot="header">这里可能是一个页面标题</h1>
<p>主要内容的一个段落。</p>
<p>另一个段落。</p>
<p slot="footer">这里有一些联系信息</p>
</app-layout>
Окончательный результат визуализации:
<div class="container">
<header>
<h1>这里可能是一个页面标题</h1>
</header>
<main>
<p>主要内容的一个段落。</p>
<p>另一个段落。</p>
</main>
<footer>
<p>这里有一些联系信息</p>
</footer>
</div>
Преимущество распределения слотов заключается в том, что оно позволяет абстрагировать компоненты в шаблоны. Сам компонент заботится только о структуре шаблона, а конкретный контент передается родительскому компоненту для обработки, в то же время он не нарушает грамматическое выражение HTML для описания структуры DOM. Я думаю, что это очень значимая технология, к сожалению, React поддерживает эту технологию не так дружелюбно. Поэтому я сослался на компонент распределения слотов Vuejs и разработал набор компонентов распределения слотов на основе React, которые могут сделать компоненты React также способными создавать шаблоны.
для<AppLayout />
компонента, я надеюсь, что это можно записать следующим образом:
class AppLayout extends React.Component {
static displayName = 'AppLayout'
render () {
return (
<div class="container">
<header>
<Slot name="header"></Slot>
</header>
<main>
<Slot></Slot>
</main>
<footer>
<Slot name="footer"></Slot>
</footer>
</div>
)
}
}
При использовании во внешнем слое это можно записать так:
<AppLayout>
<AddOn slot="header">
<h1>这里可能是一个页面标题</h1>
</AddOn>
<AddOn>
<p>主要内容的一个段落。</p>
<p>另一个段落。</p>
</AddOn>
<AddOn slot="footer">
<p>这里有一些联系信息</p>
</AddOn>
</AppLayout>
Идеи реализации компонентов
Согласно предыдущим мыслям, сначала организуйте реализацию идей.
Нетрудно заметить, что компонент распределения слотов должен опираться на два подкомпонента — компонент слотов<Slot />
и компоненты дистрибутива<AddOn />
. Слот-компонент отвечает за нагромождение и предоставление ямы для раздачи контента. Компонент распространения отвечает за сбор контента распространения и предоставление его компоненту слота для воспроизведения контента распространения, что эквивалентно потребителю слота.
Очевидно, здесь есть проблема,<Slot />
компоненты с<AddOn />
компоненты независимы, как<AddOn />
Контент для заполнения<Slot />
в? Решить эту проблему несложно, два независимых модуля должны установить соединение, поэтому постройте для них мост. Так как построить этот мост? Давайте вернемся и посмотрим на код, который мы представили ранее.
для<AppLayout />
компонент, я хочу написать его следующим образом:
class AppLayout extends React.Component {
static displayName = 'AppLayout'
render () {
return (
<div class="container">
<header>
<Slot name="header"></Slot>
</header>
<main>
<Slot></Slot>
</main>
<footer>
<Slot name="footer"></Slot>
</footer>
</div>
)
}
}
При использовании во внешнем слое напишите это так:
<AppLayout>
<AddOn slot="header">
<h1>这里可能是一个页面标题</h1>
</AddOn>
<AddOn>
<p>主要内容的一个段落。</p>
<p>另一个段落。</p>
</AddOn>
<AddOn slot="footer">
<p>这里有一些联系信息</p>
</AddOn>
</AppLayout>
Будь то<Slot />
еще<AddOn />
, на самом деле все<AppLayout />
в рамках.<Slot />
да<AppLayout />
компонентыrender()
узел компонента, возвращаемый методом, в то время как<AddOn />
является<AppLayout />
изchildren
узел, поэтому мы можем<AppLayout />
рассматривается как<Slot />
а также<AddOn />
роль моста. Так,<AppLayout />
по чем подарить<Slot />
а также<AddOn />
Как насчет подключения? Вот главный герой этой статьи -Context
. Следующий вопрос, как использоватьContext
Дать<Slot />
а также<AddOn />
строить связь?
упомянутый ранее<AppLayout />
этот мост. Во внешнем компоненте<AppLayout />
ответственный за прохождение<AddOn />
Соберите то, что заполняет слот.<AppLayout />
самопомощьContext
Определите интерфейс, который получает население. При рендеринге, потому что<Slot />
да<AppLayout />
визуализированный узел, поэтому,<Slot />
в состоянии пройтиContext
получать<AppLayout />
Определенный интерфейс для получения содержимого заполнения, а затем через этот интерфейс содержимое заполнения получается для рендеринга.
Реализовать компонент распределения слотов по задумке
из-за<AddOn />
да<AppLayout />
изchildren
узел, и<AddOn />
это конкретный компонент, мы можем передатьname
илиdisplayName
выявлено, значит,<AppLayout />
Перед рендерингом, т.е.render()
изreturn
раньше, даchildren
пройти кslot
значение какkey
, поставить каждый<AddOn />
изchildren
кэш вниз. если<AddOn />
нет настройкиslot
, а затем рассматривать его как предоставление неназванного<Slot />
Заполните контент, мы можем назначить один из этих безымянных слотовkey
, например, вызов?default
.
для<AppLayout />
, код примерно такой:
class AppLayout extends React.Component {
static childContextTypes = {
requestAddOnRenderer: PropTypes.func
}
// 用于缓存每个<AddOn />的内容
addOnRenderers = {}
// 通过Context为子节点提供接口
getChildContext () {
const requestAddOnRenderer = (name) => {
if (!this.addOnRenderers[name]) {
return undefined
}
return () => (
this.addOnRenderers[name]
)
}
return {
requestAddOnRenderer
}
}
render () {
const {
children,
...restProps
} = this.props
if (children) {
// 以k-v的方式缓存<AddOn />的内容
const arr = React.Children.toArray(children)
const nameChecked = []
this.addOnRenderers = {}
arr.forEach(item => {
const itemType = item.type
if (item.type.displayName === 'AddOn') {
const slotName = item.props.slot || '?default'
// 确保内容唯一性
if (nameChecked.findIndex(item => item === stubName) !== -1) {
throw new Error(`Slot(${slotName}) has been occupied`)
}
this.addOnRenderers[stubName] = item.props.children
nameChecked.push(stubName)
}
})
}
return (
<div class="container">
<header>
<Slot name="header"></Slot>
</header>
<main>
<Slot></Slot>
</main>
<footer>
<Slot name="footer"></Slot>
</footer>
</div>
)
}
}
<AppLayout />
определяетContext
интерфейсrequestAddOnRenderer()
,requestAddOnRenderer()
интерфейс в соответствии сname
Возвращает функцию, которая возвращает функцию на основеname
доступaddOnRenderers
характеристики,addOnRenderers
то есть<AddOn />
Объект Cache Cache.
<Slot />
Реализация очень проста, следующим образом:
// props, context
const Slot = ({ name, children }, { requestAddOnRenderer }) => {
const addOnRenderer = requestAddOnRenderer(name)
return (addOnRenderer && addOnRenderer()) ||
children ||
null
}
Slot.displayName = 'Slot'
Slot.contextTypes = { requestAddOnRenderer: PropTypes.func }
Slot.propTypes = { name: PropTypes.string }
Slot.defaultProps = { name: '?default' }
можно увидеть<Slot />
пройти черезcontext
получать<AppLayout />
предоставленный интерфейсrequestAddOnRenderer()
, основной объект финальной отрисовки кэшируется в<AppLayout />
середина<AddOn />
Содержание. Если указанный<AddOn />
содержимое, а затем визуализировать<Slot />
собственныйchildren
.
<AddOn />
проще:
const AddOn = () => null
AddOn.propTypes = { slot: PropTypes.string }
AddOn.defaultTypes = { slot: '?default' }
AddOn.displayName = 'AddOn'
<AddOn />
ничего не делать, просто вернутьсяnull
, его эффект заключается в том, чтобы позволить<AppLayout />
Кэшировать содержимое, распределенное по слотам.
может позволить<AppLayout />
более общий
Через приведенный выше код, в основном<AppLayout />
Превратился в компонент с возможностью распределения слотов, но, очевидно,<AppLayout />
Он не универсален, мы можем продвигать его в самостоятельный и универсальный компонент.
Я назвал этот компонент SlotProvider.
function getDisplayName (component) {
return component.displayName || component.name || 'component'
}
const slotProviderHoC = (WrappedComponent) => {
return class extends React.Component {
static displayName = `SlotProvider(${getDisplayName(WrappedComponent)})`
static childContextTypes = {
requestAddOnRenderer: PropTypes.func
}
// 用于缓存每个<AddOn />的内容
addOnRenderers = {}
// 通过Context为子节点提供接口
getChildContext () {
const requestAddOnRenderer = (name) => {
if (!this.addOnRenderers[name]) {
return undefined
}
return () => (
this.addOnRenderers[name]
)
}
return {
requestAddOnRenderer
}
}
render () {
const {
children,
...restProps
} = this.props
if (children) {
// 以k-v的方式缓存<AddOn />的内容
const arr = React.Children.toArray(children)
const nameChecked = []
this.addOnRenderers = {}
arr.forEach(item => {
const itemType = item.type
if (item.type.displayName === 'AddOn') {
const slotName = item.props.slot || '?default'
// 确保内容唯一性
if (nameChecked.findIndex(item => item === stubName) !== -1) {
throw new Error(`Slot(${slotName}) has been occupied`)
}
this.addOnRenderers[stubName] = item.props.children
nameChecked.push(stubName)
}
})
}
return (<WrappedComponent {...restProps} />)
}
}
}
export const SlotProvider = slotProviderHoC
Используйте компоненты высшего порядка React для<AppLayout />
Модернизируйте и превратите его в единый универсальный компонент. для оригинала<AppLayout />
, вы можете использовать этоSlotProvider
Компонент более высокого порядка, преобразованный в компонент с возможностями диспетчеризации слотов.
import { SlotProvider } from './SlotProvider.js'
class AppLayout extends React.Component {
static displayName = 'AppLayout'
render () {
return (
<div class="container">
<header>
<Slot name="header"></Slot>
</header>
<main>
<Slot></Slot>
</main>
<footer>
<Slot name="footer"></Slot>
</footer>
</div>
)
}
}
export default SlotProvider(AppLayout)
Из приведенного выше опыта мы можем видеть, что при проектировании и разработке компонента
-
Компонентам может потребоваться корневой компонент и несколько дочерних компонентов для совместной работы для выполнения функции компонента.. Например, компонент распределения слотов действительно нуждается
SlotProvider
а также<Slot />
а также<AddOn />
используется вместе,SlotProvider
как корневой компонент, а<Slot />
а также<AddOn />
Все являются подкомпонентами. -
Положение дочерних компонентов относительно корневого компонента или между дочерними компонентами не определено. для
SlotProvider
С точки зрения,<Slot />
положение неопределенное, оно будет находиться вSlotProvider
где-нибудь в шаблоне компонента этот компонент более высокого порядка обертывает, и для<Slot />
а также<AddOn />
, их прямое местонахождение также неизвестно, один находится вSlotProvider
внутри упакованного компонента находится другойSlotProvider
изchildren
. -
Подкомпоненты должны полагаться на какой-то глобальный API или данные.,Например
<Slot />
Фактическое отображаемое содержимое исходит изSlotProvider
собрал<AddOn />
Содержание.
В настоящее время нам нужно использовать посредника в качестве среды для обмена данными.По сравнению с дополнительным введением сторонних модулей, таких как redux, мы можем напрямую использоватьContext
Мог бы быть более элегантным.
Попробуйте новую версию Context API
Модернизация предыдущего диспетчера слотов с новой версией Context API.
// SlotProvider.js
function getDisplayName (component) {
return component.displayName || component.name || 'component'
}
export const SlotContext = React.createContext({
requestAddOnRenderer: () => {}
})
const slotProviderHoC = (WrappedComponent) => {
return class extends React.Component {
static displayName = `SlotProvider(${getDisplayName(WrappedComponent)})`
// 用于缓存每个<AddOn />的内容
addOnRenderers = {}
requestAddOnRenderer = (name) => {
if (!this.addOnRenderers[name]) {
return undefined
}
return () => (
this.addOnRenderers[name]
)
}
render () {
const {
children,
...restProps
} = this.props
if (children) {
// 以k-v的方式缓存<AddOn />的内容
const arr = React.Children.toArray(children)
const nameChecked = []
this.addOnRenderers = {}
arr.forEach(item => {
const itemType = item.type
if (item.type.displayName === 'AddOn') {
const slotName = item.props.slot || '?default'
// 确保内容唯一性
if (nameChecked.findIndex(item => item === stubName) !== -1) {
throw new Error(`Slot(${slotName}) has been occupied`)
}
this.addOnRenderers[stubName] = item.props.children
nameChecked.push(stubName)
}
})
}
return (
<SlotContext.Provider value={
requestAddOnRenderer: this.requestAddOnRenderer
}>
<WrappedComponent {...restProps} />
</SlotContext.Provider>
)
}
}
}
export const SlotProvider = slotProviderHoC
удалил предыдущийchildContextTypes
а такжеgetChildContext()
, за исключением локальных корректировок, в общем ядре серьезных изменений нет.
// Slot.js
import { SlotContext } from './SlotProvider.js'
const Slot = ({ name, children }) => {
return (
<SlotContext.Consumer>
{(context) => {
const addOnRenderer = requestAddOnRenderer(name)
return (addOnRenderer && addOnRenderer()) ||
children ||
null
}}
</SlotContext.Consumer>
)
}
Slot.displayName = 'Slot'
Slot.propTypes = { name: PropTypes.string }
Slot.defaultProps = { name: '?default' }
Поскольку раньше он использовался в соответствии с моделью производитель-потребитель.Context
И сам компонент относительно прост, поэтому после использования новой API для преобразования не так много различий.
Суммировать
- в сравнении с
props
а такжеstate
, РеагироватьContext
Компонентная связь между уровнями может быть достигнута. - Использование Context API основано на схеме производитель-потребитель. На стороне производителя через статические свойства компонента
childContextTypes
объявление, затем через метод экземпляраgetChildContext()
СоздайтеContext
объект. Со стороны потребителя через статические свойства компонентовcontextTypes
для примененияContext
свойства, а затем передать экземплярcontext
доступContext
характеристики. - использовать
Context
Нужно больше думать, не рекомендуется в приложенииContext
, но если связность компонента может быть обеспечена в процессе разработки компонента, управляема и ремонтопригодна, без разрушения зависимостей дерева компонентов, а сфера влияния невелика, можно рассмотреть возможность использованияContext
решить некоторые проблемы. - пройти через
Context
Предоставление доступа к API может в определенной степени облегчить решение некоторых проблем, но я лично считаю, что это не очень хорошая практика, и с ней нужно быть осторожным. - старая версия
Context
Для обновления требуются зависимостиsetState()
, ненадежен, но эта проблема фиксируется в новой версии API. - можно поставить
Context
Относитесь к этому как к области действия компонента, но нужно обратить вниманиеContext
Прежде чем использовать его, проанализируйте, действительно ли необходимо его использовать, чтобы избежать некоторых побочных эффектов, вызванных чрезмерным использованием. - можно поставить
Context
В качестве средства обмена данными на уровне приложения или компонента. - Спроектируйте и разработайте компонент. Если этот компонент необходимо объединить с несколькими компонентами, используйте
Context
Может быть, более элегантно.
Выше это то, что я поделился.Если есть какие-то недостатки или ошибки, пожалуйста, покритикуйте и исправьте их.
Цитировать
- Context - https://reactjs.org/docs/context.html
- React 16.3 уже здесь: с новым Context API — http://cnodejs.org/topic/5a7bd5c4497a08f571384f03
- Content Distribution with Slots - https://vuejs.org/v2/guide/components.html#Content-Distribution-with-Slots
- Карта названия - http://www.mafengwo.cn/poi/36580.html