что такое реакция-редукс
react-redux
даredux
официальныйReact
библиотека привязки. Это помогает нам соединить слой пользовательского интерфейса и уровень данных. Цель этой статьи не в том, чтобы представитьreact-redux
использовать, а реализовать простойreact-redux
, я надеюсь, что смогу вам помочь.
Сначала подумайте об этом, если вы не используетеreact-redux
,нашreact
Как совместить в проектеredux
развивать.
каждый требует
redux
Комбинируя используемые компоненты, нам всем нужно сделать следующее:
- попасть в компонент
store
государство в - монитор
store
При изменении состояния обновите компонент при изменении состояния. - Когда компонент размонтирован, удалите прослушиватель изменений состояния.
следующим образом:
import React from 'react';
import store from '../store';
import actions from '../store/actions/counter';
/**
* reducer 是 combineReducer({counter, ...})
* state 的结构为
* {
* counter: {number: 0},
* ....
* }
*/
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
number: store.getState().counter.number
}
}
componentDidMount() {
this.unsub = store.subscribe(() => {
if(this.state.number === store.getState().counter.number) {
return;
}
this.setState({
number: store.getState().counter.number
});
});
}
render() {
return (
<div>
<p>{`number: ${this.state.number}`}</p>
<button onClick={() => {store.dispatch(actions.add(2))}}>+</button>
<button onClick={() => {store.dispatch(actions.minus(2))}}>-</button>
<div>
)
}
componentWillUnmount() {
this.unsub();
}
}
Если у нас есть много компонентов в нашем проекте, которые необходимо объединить сredux
Комбинированное использование, то эти компоненты нужно многократно писать эту логику. Очевидно, нам нужно найти способ переиспользовать эту часть логики, иначе будет казаться, что мы тупые. мы знаем,react
Компоненты среднего и высокого уровня могут реализовать повторное использование логики.
используется в [Counter
код] (GitHub.com/Иветт Л.А. Ю/Б…) серединаmyreact-redux/counter
, рекомендуется сначалаclone
Код, конечно, если вы считаете, что эта статья хороша, поставьте звезду, чтобы поощрить ее.
логическое мультиплексирование
существуетsrc
Создайте новый в каталогеreact-redux
Папка, последующие файлы создаются в этой папке.
Создайте файл connect.js
файл, созданный вreact-redux/components
Под папкой:
Мы будем писать повторяющуюся логикуconnect
середина.
import React, { Component } from 'react';
import store from '../../store';
export default function connect (WrappedComponent) {
return class Connect extends Component {
constructor(props) {
super(props);
this.state = store.getState();
}
componentDidMount() {
this.unsub = store.subscribe(() => {
this.setState({
this.setState(store.getState());
});
});
}
componentWillUnmount() {
this.unsub();
}
render() {
return (
<WrappedComponent {...this.state} {...this.props}/>
)
}
}
}
Есть небольшая проблема, хотя логика повторяется, данные, требуемые каждым компонентом, разные, и все состояния не должны передаваться компоненту, поэтому мы надеемся, что при вызовеconnect
Когда может быть уведомлен требуемый контент статусаconnect
. Кроме того, компоненту также может потребоваться изменить состояние, а затем также сообщитьconnect
, какие действия необходимо отправить, иначеconnect
Невозможно узнать, какие действия связать с вами.
Для этого добавим два новых параметра:mapStateToProps
а такжеmapDispatchToProps
, эти два параметра отвечают за сообщениеconnect
требуемый компонентstate
Содержимое и действия, которые необходимо отправить.
mapStateToProps и mapDispatchToProps
мы знаемmapStateToProps
а такжеmapDispatchToProps
Какова роль, но пока нам не ясно, в каком формате эти два параметра должны быть переданыconnect
использовать.
import { connect } from 'react-redux';
....
//connect 的使用
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
-
mapStateToProps сообщает
connect
, состояние, к которому компонент должен привязаться.mapStateToProps
Нужно выбрать состояние, в котором нуждается компонент, из всего состояния, но вызватьconnect
, мы не можем получитьstore
,ноconnect
Доступно внутриstore
, для этого будемmapStateToProps
определяется как функция вconnect
вызывая его внутренне, будетstore
серединаstate
перейти к нему, а затем передать результат, возвращенный функцией, компоненту в качестве свойства. компонент черезthis.props.XXX
чтобы получить. следовательно,mapStateToProps
Формат должен выглядеть следующим образом://将 store.getState() 传递给 mapStateToProps mapStateToProps = state => ({ number: state.counter.number });
-
mapDispatchToProps сообщает
connect
, действие, к которому компонент должен привязаться.Напомним, что действие отправляется в компоненте:
store.dispatch({actions.add(2)})
.connect
После упаковки мы все еще должны иметь возможность отправлять действия, определенноthis.props.XXX()
такой формат.Например, счетчик увеличивается, вызывает
this.props.add(2)
, необходимо раздатьstore.dispatch({actions.add(2)})
,следовательноadd
атрибут, соответствующий контент(num) => { store.dispatch({actions.add(num)}) }
. Свойства, передаваемые компоненту, выглядят следующим образом:{ add: (num) => { store.dispatch(actions.add(num)) }, minus: (num) => { store.dispatch(actions.minus(num)) } }
а также
mapStateToProps
то же самое при звонкеconnect
, мы не можем получитьstore.dispatch
, так что нам тоже нужноmapDispatchToProps
разработан как функция, вconnect
внутренний вызов, так чтоstore.dispatch
перейти к нему. так,mapStateToProps
Он должен быть в следующем формате://将 store.dispacth 传递给 mapDispatchToProps mapDispatchToProps = (dispatch) => ({ add: (num) => { dispatch(actions.add(num)) }, minus: (num) => { dispatch(actions.minus(num)) } })
Пока что мы выяснилиmapStateToProps
а такжеmapDispatchToProps
Формат - время дальнейшего совершенствованияconnect
.
подключить версию 1.0
import React, { Component } from 'react';
import store from '../../store';
export default function connect (mapStateToProps, mapDispatchToProps) {
return function wrapWithConnect (WrappedComponent) {
return class Connect extends Component {
constructor(props) {
super(props);
this.state = mapStateToProps(store.getState());
this.mappedDispatch = mapDispatchToProps(store.dispatch);
}
componentDidMount() {
this.unsub = store.subscribe(() => {
const mappedState = mapStateToProps(store.getState());
//TODO 做一层浅比较,如果状态没有改变,则不setState
this.setState(mappedState);
});
}
componentWillUnmount() {
this.unsub();
}
render() {
return (
<WrappedComponent {...this.props} {...this.state} {...this.mappedDispatch} />
)
}
}
}
}
мы знаем,connect
как естьreact-redux
методы, предоставляемые библиотекой, поэтому мы не можем напрямуюconnect.js
импортироватьstore
,этоstore
должны использоватьсяreact-redux
поступление заявки.react
Существует два типа передачи данных: через атрибутыprops
или через объект контекстаcontext
,пройти черезconnect
Обернутые компоненты распределяются по приложению, аcontext
Он предназначен для обмена данными, которые являются «глобальными» для дерева компонентов.
нам нужно поставитьstore
помещатьcontext
, чтобы можно было получить все компоненты-потомки корневого компонентаstore
. Этой части контента мы можем конечно написать соответствующий код в приложении, но очевидно, что эти коды повторяются в каждом приложении. Поэтому мы инкапсулируем эту часть вreact-redux
внутренний.
Здесь мы используем старыйContext API
для записи (учитывая код, который мы реализовали в ветке react-redux 4.x, поэтому мы используем устаревший контекстный API).
Provider
нам нужно предоставитьProvider
Компонент, его функция — получение перевода от приложенияstore
, держисьcontext
, так что его компоненты-потомки могут быть получены через объект контекстаstore
.
Создайте новый файл Provider.js
файл, созданный вreact-redux
В папке:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class Provider extends Component {
static childContextTypes = {
store: PropTypes.shape({
subscribe: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired,
getState: PropTypes.func.isRequired
}).isRequired
}
constructor(props) {
super(props);
this.store = props.store;
}
getChildContext() {
return {
store: this.store
}
}
render() {
/**
* 早前返回的是 return Children.only(this.props.children)
* 导致Provider只能包裹一个子组件,后来取消了此限制
* 因此此处,我们直接返回 this.props.children
*/
return this.props.children
}
}
Создайте новый файл index.js
файл, созданный вreact-redux
Под содержанием:
Этот файл делает только одну вещь, т.connect
а такжеProvider
экспорт
import connect from './components/connect';
import Provider from './components/Provider';
export {
connect,
Provider
}
Использование провайдера
При использовании нам нужно только ввестиProvider
,Будуstore
Перейти кProvider
.
import React, { Component } from 'react';
import { Provider } from '../react-redux';
import store from './store';
import Counter from './Counter';
export default class App extends Component {
render() {
return (
<Provider store={store}>
<Counter />
</Provider>
)
}
}
Слишком далеко,Provider
Исходный код и использование были ясно объяснены, но соответствующиеconnect
Требуются также некоторые модификации, для общности нужно начать сcontext
поднимись и получиstore
, заменив предыдущий импорт.
подключить версию 2.0
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default function connect(mapStateToProps, mapDispatchToProps) {
return function wrapWithConnect(WrappedComponent) {
return class Connect extends Component {
//PropTypes.shape 这部分代码与 Provider 中重复,因此后面我们可以提取出来
static contextTypes = {
store: PropTypes.shape({
subscribe: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired,
getState: PropTypes.func.isRequired
}).isRequired
}
constructor(props, context) {
super(props, context);
this.store = context.store;
//源码中是将 store.getState() 给了 this.state
this.state = mapStateToProps(this.store.getState());
this.mappedDispatch = mapDispatchToProps(this.store.dispatch);
}
componentDidMount() {
this.unsub = this.store.subscribe(() => {
const mappedState = mapStateToProps(this.store.getState());
//TODO 做一层浅比较,如果状态没有改变,则无需 setState
this.setState(mappedState);
});
}
componentWillUnmount() {
this.unsub();
}
render() {
return (
<WrappedComponent {...this.props} {...this.state} {...this.mappedDispatch} />
)
}
}
}
}
использоватьconnect
ассоциацияCounter
а такжеstore
данные в .
import React, { Component } from 'react';
import { connect } from '../react-redux';
import actions from '../store/actions/counter';
class Counter extends Component {
render() {
return (
<div>
<p>{`number: ${this.props.number}`}</p>
<button onClick={() => { this.props.add(2) }}>+</button>
<button onClick={() => { this.props.minus(2) }}>-</button>
</div>
)
}
}
const mapStateToProps = state => ({
number: state.counter.number
});
const mapDispatchToProps = (dispatch) => ({
add: (num) => {
dispatch(actions.add(num))
},
minus: (num) => {
dispatch(actions.minus(num))
}
});
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
store/actions/counter.js определяется следующим образом:
import { INCREMENT, DECREMENT } from '../action-types';
const counter = {
add(number) {
return {
type: INCREMENT,
number
}
},
minus(number) {
return {
type: DECREMENT,
number
}
}
}
export default counter;
До сих пор нашreact-redux
Библиотека готова к использованию, но осталось проработать много деталей:
-
mapDispatchToProps
Определение немного громоздко писать и недостаточно лаконично ты помнишьredux
серединаbindActionCreators
, с помощью этого метода мы можем разрешить передачуactionCreator
Датьconnect
, затем вconnect
Конвертировать внутри. -
connect
а такжеProvider
серединаstore
изPropType
Правила могут быть извлечены, чтобы избежать избыточности кода -
mapStateToProps
а такжеmapDispatchToProps
Можно указать значение по умолчаниюmapStateToProps
По умолчаниюstate => ({})
; не относитсяstate
;mapDispatchToProps
Значение по умолчаниюdispatch => ({dispatch})
,Будуstore.dispatch
Методы передаются как свойства обернутому свойству. -
В настоящее время мы проходим только
store.getState()
ДатьmapStateToProps
, но вполне вероятно, что фильтрация нужногоstate
Когда его необходимо обрабатывать в соответствии с свойствами самого компонента, вы также можете пройти свойства компонента, чтобыmapStateToProps
, по той же причине, также передает свои свойства вmapDispatchToProps
.
подключить версию 3.0
мы будемstore
Правила PropType извлекаются и помещаются вutils/storeShape.js
в файле.
Код поверхностного сравнения находится вutils/shallowEqual.js
В файле не указана общая функция поверхностного сравнения, если вам интересно, вы можете прочитать код напрямую.
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import storeShape from '../utils/storeShape';
import shallowEqual from '../utils/shallowEqual';
/**
* mapStateToProps 默认不关联state
* mapDispatchToProps 默认值为 dispatch => ({dispatch}),将 `store.dispatch` 方法作为属性传递给组件
*/
const defaultMapStateToProps = state => ({});
const defaultMapDispatchToProps = dispatch => ({ dispatch });
export default function connect(mapStateToProps, mapDispatchToProps) {
if(!mapStateToProps) {
mapStateToProps = defaultMapStateToProps;
}
if (!mapDispatchToProps) {
//当 mapDispatchToProps 为 null/undefined/false...时,使用默认值
mapDispatchToProps = defaultMapDispatchToProps;
}
return function wrapWithConnect(WrappedComponent) {
return class Connect extends Component {
static contextTypes = {
store: storeShape
};
constructor(props, context) {
super(props, context);
this.store = context.store;
//源码中是将 store.getState() 给了 this.state
this.state = mapStateToProps(this.store.getState(), this.props);
if (typeof mapDispatchToProps === 'function') {
this.mappedDispatch = mapDispatchToProps(this.store.dispatch, this.props);
} else {
//传递了一个 actionCreator 对象过来
this.mappedDispatch = bindActionCreators(mapDispatchToProps, this.store.dispatch);
}
}
componentDidMount() {
this.unsub = this.store.subscribe(() => {
const mappedState = mapStateToProps(this.store.getState(), this.props);
if (shallowEqual(this.state, mappedState)) {
return;
}
this.setState(mappedState);
});
}
componentWillUnmount() {
this.unsub();
}
render() {
return (
<WrappedComponent {...this.props} {...this.state} {...this.mappedDispatch} />
)
}
}
}
}
Теперь нашconnect
разрешатьmapDispatchToProps
это функция илиactionCreators
объект, вmapStateToProps
а такжеmapDispatchToProps
по умолчанию илиnull
также может хорошо работать.
Но есть еще одна проблема,connect
Все возвращаемые имена компонентовConnect
, который нелегко отлаживать. Таким образом, мы можем добавитьdisplayName
.
подключить версию 4.0
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import storeShape from '../utils/storeShape';
import shallowEqual from '../utils/shallowEqual';
/**
* mapStateToProps 缺省时,不关联state
* mapDispatchToProps 缺省时,设置其默认值为 dispatch => ({dispatch}),将`store.dispatch` 方法作为属性传递给组件
*/
const defaultMapStateToProps = state => ({});
const defaultMapDispatchToProps = dispatch => ({ dispatch });
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
export default function connect(mapStateToProps, mapDispatchToProps) {
if(!mapStateToProps) {
mapStateToProps = defaultMapStateToProps;
}
if(!mapDispatchToProps) {
//当 mapDispatchToProps 为 null/undefined/false...时,使用默认值
mapDispatchToProps = defaultMapDispatchToProps;
}
return function wrapWithConnect (WrappedComponent) {
return class Connect extends Component {
static contextTypes = storeShape;
static displayName = `Connect(${getDisplayName(WrappedComponent)})`;
constructor(props) {
super(props);
//源码中是将 store.getState() 给了 this.state
this.state = mapStateToProps(store.getState(), this.props);
if(typeof mapDispatchToProps === 'function') {
this.mappedDispatch = mapDispatchToProps(store.dispatch, this.props);
}else{
//传递了一个 actionCreator 对象过来
this.mappedDispatch = bindActionCreators(mapDispatchToProps, store.dispatch);
}
}
componentDidMount() {
this.unsub = store.subscribe(() => {
const mappedState = mapStateToProps(store.getState(), this.props);
if(shallowEqual(this.state, mappedState)) {
return;
}
this.setState(mappedState);
});
}
componentWillUnmount() {
this.unsub();
}
render() {
return (
<WrappedComponent {...this.props} {...this.state} {...this.mappedDispatch} />
)
}
}
}
}
Слишком далеко,react-redux
В принципе мы это реализовали, но код не идеален, например,ref
пропущенный вопрос, компонентprops
При изменении пересчитатьthis.state
а такжеthis.mappedDispatch
, без дальнейшей оптимизации производительности и т. д. На этой основе можно действовать дальше.
react-redux
Код из магистральной ветки уже используетсяhooks
Перепишите, если будет время позже, будет выведена новая версия разбора кода.
Наконец, используйте наши собственные письменныеreact-redux
а такжеredux
написаноTodo
Демо, функция нормальная, код вhttps://github.com/YvetteLau/Blog
серединаmyreact-redux/todo
Вниз.
Прикрепите старый и новыйcontext API
Как использовать:
context
На данный момент существует две версииcontext API
, старый API будет поддерживаться во всех выпусках 16.x, но будет удален в будущих выпусках.
контекстный API (новый)
const MyContext = React.createContext(defaultValue);
СоздаватьContext
объект. когдаReact
сделать подписку на этоContext
Компонент объекта, этот компонент будет сопоставляться с ближайшим к себе в дереве компонентовProvider
читать текущийcontext
стоимость.
Примечание: только если дерево, в котором находится компонент, не совпадаетProvider
когда этоdefaultValue
параметры вступят в силу.
использовать
Context.js
Сначала создайте объект Context
import React from 'react';
const MyContext = React.createContext(null);
export default MyContext;
Корневые компоненты (Panel.js)
- Установите контент, которым нужно поделиться в
<MyContext.Provider>
изvalue
in (т.е. значение контекста) - подкомпоненты
<MyContext.Provider>
пакет
import React from 'react';
import MyContext from './Context';
import Content from './Content';
class Pannel extends React.Component {
state = {
theme: {
color: 'rgb(0, 51, 254)'
}
}
render() {
return (
// 属性名必须叫 value
<MyContext.Provider value={this.state.theme}>
<Content />
</MyContext.Provider>
)
}
}
Компонент-потомок ( Content.js )
компонент класса
- определение
Class.contextType
:static contextType = ThemeContext
; - пройти через
this.context
Получать<ThemeContext.Provider>
серединаvalue
содержание (т.context
стоимость)
//类组件
import React from 'react';
import ThemeContext from './Context';
class Content extends React.Component {
//定义了 contextType 之后,就可以通过 this.context 获取 ThemeContext.Provider value 中的内容
static contextType = ThemeContext;
render() {
return (
<div style={{color: `2px solid ${this.context.color}`}}>
//....
</div>
)
}
}
функциональный компонент
- дочерние элементы завернуты в
<ThemeContext.Consumer>
середина -
<ThemeContext.Consumer>
Дочерний элемент — функция, входной параметрcontext
стоимость(Provider
который предоставилvalue
). вот{color: XXX}
import React from 'react';
import ThemeContext from './Context';
export default function Content() {
return (
<ThemeContext.Consumer>
{
context => (
<div style={{color: `2px solid ${context.color}`}}>
//....
</div>
)
}
</ThemeContext.Consumer>
)
}
контекстный API (старый)
использовать
- определить корневой компонент
childContextTypes
(проверятьgetChildContext
тип возврата) - определение
getChildContext
метод
Корневой компонент ( Pannel.js )
import React from 'react';
import PropTypes from 'prop-types';
import Content from './Content';
class Pannel extends React.Component {
static childContextTypes = {
theme: PropTypes.object
}
getChildContext() {
return { theme: this.state.theme }
}
state = {
theme: {
color: 'rgb(0, 51, 254)'
}
}
render() {
return (
// 属性名必须叫 value
<>
<Content />
</>
)
}
}
Компонент-потомок ( Content.js )
- определить компоненты-потомки
contextTypes
(объявление и проверка типа состояния, которое необходимо получить) - Переданный контент контекста можно получить через this.context.
import React from 'react';
import PropTypes from 'prop-types';
class Content extends React.Component {
static contextTypes = {
theme: PropTypes.object
};
render() {
return (
<div style={{color: `2px solid ${this.context.theme.color}`}}>
//....
</div>
)
}
}
Ссылка на ссылку:
- исходный код реакции-редукции:GitHub.com/Горячее цинкование это/Горячее…
- [Paoding Jie Niu React-Redux (2): подключиться]nuggets.capable/post/684490…
- [Научитесь собирать колеса вместе (3): напишите React-Redux с нуля]nuggets.capable/post/684490…