Эта статья является третьей в серии обучающих колес вместе.В этой статье мы напишем React-Redux с нуля.В этой серии статей мы выберем некоторые классические фронтальные колеса для анализа исходного кода и постепенно реализуем их с нуля В этой серии вы узнаете, как реализовать интерфейсные классические колеса, такие как Promises/A+, Redux, react-redux, vue, dom-diff, webpack, babel, kao, express, async/await, jquery, Lodash, requirejs, lib-flexible и т. д. Исходный код главы размещен на github, обратите внимание~
Похожие серии статей:
Научитесь строить колеса вместе (1): с нуля напишите обещание, соответствующее спецификации Promises/A+.
Научитесь собирать колеса вместе (2): напишите Redux с нуля
Научитесь строить колеса вместе (3): напишите React-Redux с нуля
Эта серия репозиториев github:
Давайте вместе изучим серию колес на github (добро пожаловать, звезда~)
предисловие
В прошлой главе мы написали redux, когда redux комбинируется с react, для удобства обычно используется react-redux.
Эта библиотека не является обязательной. В реальных проектах вы должны взвесить, использовать ли Redux напрямую или использовать React-Redux. Последний обеспечивает удобство, но требует освоения дополнительных API и соблюдения спецификаций разделения компонентов.
В этой статье не будет слишком много информации об использовании react-redux, и основное внимание будет по-прежнему уделено реализации исходного кода. Если вы не знаете, как его использовать, вы можете прочитать соответствующие статьи, чтобы узнать.
рекомендуемая статья:
Начало работы с Redux: использование React-Redux
Весь код в этой статье имеет репозиторий кода на github, вы можетеНажмите здесь, чтобы просмотретьКод этой статьи, вы также можете пометить звездочкой~
Начинать
context
Прежде чем говорить о React-Redux, давайте поговорим о контексте в React.js. Контекст в React.js всегда считался нестабильной, опасной фичей, которая может быть удалена и не описана в официальной документации сайта, но очень удобна в использовании, например, у нас очень большое дерево компонентов. мы хотим изменить состояние и заставить все компоненты действовать, когда мы не используем избыточность, нам нужно передавать реквизиты слой за слоем. А вот с контекстом все очень просто. Пока компонент помещает некоторое состояние в свой собственный контекст, все подкомпоненты этого компонента могут напрямую обращаться к этому состоянию, не проходя через промежуточные компоненты.
Например, есть такое дерево компонентов:
Данные пользовательской информации userinfo нужны многим компонентам, поэтому мы получаем их из Index корневого узла согласно обычной идее, а затем передаем это состояние через props слой за слоем, и, наконец, все компоненты получают userinfo и используют его. Но с этим есть проблема:Если иерархия компонентов глубокая, использование props для передачи значений вниз — катастрофа.
Мы подумали, что было бы здорово, если бы это дерево компонентов могло совместно использовать этот репозиторий состояний глобально, и мы могли бы обращаться к репозиторию состояний, чтобы получить соответствующее состояние, когда захотим, без необходимости передавать его вручную.
Контекст React.js - это такая вещь. Пока компонент помещает какое-то состояние в свой собственный контекст, все подкомпоненты этого компонента могут напрямую обращаться к этому состоянию, не проходя через промежуточные компоненты. Давайте посмотрим, как это использовать. ://在根组件上将userInfo放入context
class Index extends Component {
static childContextTypes = {
userInfo: PropTypes.object
}
constructor() {
super()
this.state = {
userInfo: {
name:"小明",
id:17
}
}
}
getChildContext() {
return { userInfo: this.state.userInfo }
}
render() {
return ( <div >
<Header/>
</div>
)
}
}
class Header extends Component {
render() {
return ( <div>
<Title/>
</div>
)
}
}
class Title extends Component {
static contextTypes = {
title: PropTypes.object
}
render() {
// 无论组件层级有多深,子组件都可以直接从context属性获取状态
return ( <h1> 欢迎{ this.context.userInfo.name } </h1>)
}
}
Выше мы определили userInfo в корневом компоненте Index и смонтировали его в контексте Index, после чего, сколько бы ни было слоев подкомпонентов ниже, можно напрямую получить состояние заголовка из контекста.
Так зачем использовать избыточность для управления глобальным состоянием, если контекст так удобен?
Потому что данные в контексте можно свободно трогать и изменять по желанию, что приводит к непредсказуемой работе программы. Вот почему контекст всегда считался устаревшим. Хотя избыточность громоздка в использовании, она может сделать поведение при изменении данных предсказуемым и отслеживаемым, потому что в избыточности вы должны выполнять определенные разрешенные модификации посредством диспетчеризации. И выполняемая операция должна быть четко объявлено в акции заранее.
Итак, можем ли мы объединить преимущества обоих, чтобы мы могли безопасно и легко управлять глобальным состоянием?
React-Redux
React-Redux — автор Redux, который инкапсулирует специальную библиотеку для React.Чтобы пользователям React было проще использовать Redux, мы обычно пишем это, когда используем React-Redux:// root.js
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import userReducer from 'reducers/userReducer'
import Header from 'containers/header'
const store = createStore(userReducer)
export default class Root extends Component {
render() {
return (<div>
<Header></Header>
</div>
);
};
}
ReactDOM.render( <Provider store = { store } >
<Root/>
</Provider>,
document.getElementById('root'));
//containers/header.js
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import * as userinfoActions from 'actions/userinfo.js';
import fetch from 'isomorphic-fetch'
class Header extends Component {
constructor() {
super();
this.state = {
username:""
}
}
componentDidMount(){
this.getUserInfo()
}
getUserInfo(){
fetch("/api/pay/getUserInfo")
.then(response => {
return response.json()
})
.then(json =>{
this.props.userinfoActions.login(data);
this.setState({username: data.username});
})
.catch(e => {
console.log(e)
})
}
render(){
return (
<div>
欢迎用户{this.state.username}
</div>
);
}
}
function mapStateToProps(state) {
return { userinfo: state.userinfo }
}
function mapDispatchToProps(dispatch) {
return {
userinfoActions: bindActionCreators(userinfoActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Header)
// reducers/userReducer.js
export default function userinfo(state = {}, action) {
switch (action.type) {
case "USERINFO_LOGIN":
return action.data
default:
return state
}
}
// actions/useraction.js
export function login(data) {
return {
type: "USERINFO_LOGIN",
data
}
}
Выше приведен простой сценарий.После входа на страницу для получения информации о пользователе имя пользователя в информации о пользователе отображается в заголовке страницы.Поскольку необходимо использовать несколько компонентов информации о пользователе, не только главный компонент используется, поэтому он используется в редукции.
Мы видим, что после использования react-redux мы в основном используем в нем две вещи, одна — Provider, а другая — connect.Кроме того, нам нужно определить две функции mapStateToProps, mapDispatchToProps и передать их для подключения.Далее мы поговорим об этом отдельно.Что делают вещи и как их достичь.
Provider
Давайте сначала взглянем на Provider.Provider является компонентом высокого порядка.Мы видим, что при его использовании он будет обернут вне корневого компонента, а store будет передан в качестве его реквизита.Его функция заключается в Сделайте себя корневым компонентом всех компонентов, затем смонтируйте хранилище в его контексте, чтобы все подкомпоненты под ним могли совместно использовать глобальное состояние. Давайте посмотрим, как это сделать:
// Provider.js
import React, { Component } from 'react';
import propTypes from 'prop-types';
export default class Provider extends Component {
static childContextTypes = {
store: propTypes.object.isRequired
}
getChildContext() {
return { store: this.props.store };
}
render() {
return this.props.children;
}
}
Это относительно легко реализовать. Напишите компонент Provider, смонтируйте хранилище в контексте Provider, а затем оберните Provider вне корневого компонента при его использовании. Поскольку Provider является родительским компонентом исходного корневого компонента, он становится настоящим Корневой компонент, все следующие подкомпоненты могут получить доступ к хранилищу через контекст, компонент Provider использует функцию контекста для решения проблемы, заключающейся в том, что каждому компоненту в проекте необходимо импортировать хранилище для использования избыточности, что значительно повышает удобство.
connect
Прежде всего, давайте подумаем, можно ли использовать только Provider.Конечно, поскольку хранилище было смонтировано в контекст на корневом компоненте, все подкомпоненты могут получить доступ к хранилищу через контекст, а затем использовать состояние в хранить и отправлять его с диспетчеризацией хранилища Действие обновляет состояние, но это все равно немного неудобно, потому что каждый компонент слишком зависит от контекста, из-за чего логика компонента имеет дело с хранилищем, а логика сам компонент должен быть соединен вместе, что делает компонент непригодным для использования.
Наше идеальное состояние состоит в том, что рендеринг компонента зависит только от свойств, переданных из внешнего мира и его собственного состояния, и не зависит ни от каких данных из другого внешнего мира.Такой компонент имеет самую сильную возможность повторного использования. Как отделить логику компонента, работающего с магазином, от логики самого компонента? Ответ заключается в использовании компонентов более высокого порядка. Мы оборачиваем исходные написанные бизнес-компоненты (такие как заголовок, список и т. д.) слоем компонентов, чтобы компоненты имели дело с хранилищем.Часть компонента помещается во внешний компонент, внутренний компонент отвечает только за свою логику, а внешний компонент общается с внутренним компонентом через реквизиты, так что место где компонент взаимодействует с хранилищем, он отделен от сущности компонента, как слой оболочки, которую мы можем поместить. Сущность компонента можно повторно использовать где угодно, нужно только изменить оболочку, функция подключения отвечает за выполнение вышеуказанного.
mapDispatchToProps — второй параметр функции подключения, который используется для сопоставления параметров компонентов пользовательского интерфейса с методом store.dispatch. То есть он определяет, какие операции пользователя должны быть переданы в Магазин как Действия. Это может быть функция или объект.
Эти две функции можно просто понять как требования сущности внутреннего компонента для компонента внешней оболочки.Сущность компонента сообщает компоненту оболочки, какие состояния хранить через mapStateToProps, а компонент оболочки переходит в хранилище, чтобы получить его, а затем передает его. в виде реквизита.Сущности компонента, mapDispatchToProps то же самое.
Кроме того, мы обычно пишем это при использовании connectexport default connect(mapStateToProps,mapDispatchToProps)(Header)
, поэтому функция подключения должна сначала получить две функции mapStateToProps и mapDispatchToProps, а затем вернуть функцию, параметр возвращаемой функции получает компонент для упаковки, и, наконец, функция выполняет возвращенный упакованный компонент.
Некоторые друзья могут спросить, почему бы не напрямуюconnect(mapStateToProps,mapDispatchToProps,Header)
, и он разделен на две функции для написания, потому что React-redux официально разработан таким образом.Я лично думаю, что автор хочет улучшить возможность повторного использования функции подключения.Его реализация кода.
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import propTypes from 'prop-types';
export default function connect(mapStateToProps, mapDispatchToProps) {
return function(WrapedComponent) {
//壳组件
class ProxyComponent extends Component {
static contextTypes = {
store: propTypes.object
}
constructor(props, context) {
super(props, context);
this.store = context.store;
this.state = mapStateToProps(this.store.getState());
}
componentWillMount() {
this.store.subscribe(() => {
this.setState(mapStateToProps(this.store.getState()));
});
}
render() {
let actions = {};
if (typeof mapDispatchToProps == 'function') {
actions = mapDispatchToProps(this.store.disaptch);
} else if (typeof mapDispatchToProps == 'object') {
actions = bindActionCreators(mapDispatchToProps, this.store.dispatch);
}
//壳组件内部渲染真正的组件实体,并将业务组件想要的store里的状态及想要触发的action以props形式传入
return <WrapedComponent {...this.state } {...actions}
/>
}
}
return ProxyComponent;
}
}
Давайте посмотрим, что делает функция подключения?
- Сначала получают mapStateToProps, mapDispatchToProps и возвращают функцию, возвращаемая функция получает компонент.
- Объявляется компонент оболочки ProxyComponent, и объект хранилища получается через контекст.
- Затем в конструкторе через входящую функцию mapStateToProps через метод getState в объекте хранилища, полученном на предыдущем шаге и сохраненном в состоянии компонента оболочки, получается желаемое состояние сущности компонента.
- В жизненном цикле компонента оболочки componentWillMount прописать функцию обратного вызова при изменении состояния хранилища: хранилище изменяется, синхронно обновляет собственное состояние до последнего состояния, согласующегося с состоянием в хранилище.
- Инкапсулируйте соответствующие действия компонента, который будет отправлен, используя диспетчеризацию в функциях. На этом шаге мы рассмотрим, как это сделать.Во-первых, определите, является ли mapDispatchToProps функцией или объектом, потому что мы обычно используем mapDispatchToProps в двух распространенных способах написания.Один из них заключается в передаче функции в позиции параметра mapDispatchToProps:
function mapDispatchToProps(dispatch) {
return {
userinfoActions: bindActionCreators(userinfoActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Header)
Другой - передать объект создателя действия напрямую.
export default connect(mapStateToProps, ...userinfoActions )(Header)
Мы должны гарантировать, что независимо от того, является ли mapDispatchToProps, переданный пользователем, функцией или объектом создателя действия, мы должны позволить пользователю отправить действие в объекте компонента, используя метод this.props.xxx(), не касаясь напрямую отправка метода хранения.
Итак, нам нужно использовать метод redux bindActionCreators,
из этой серииВторая статья Учимся собирать колеса вместе (2): пишем Redux с нуляЗдесь представлен принцип реализации этого метода, который позволяет нам представлять действия в виде методов, и в то же время автоматически отправлять соответствующие действия. Итак, мы видим, что когда пользователь передает функцию, пользователь использует bindActionCreators внутри функции mapDispatchToProps для преобразования создателей действий в методы один за другим, и если объект создателя действия передается напрямую, то мы используем внутри bindActionCreators. подключение для преобразования Создатель входящего действия преобразуется в метод «один за другим», то есть, если пользователь не выполняет этот шаг, то react-redux сделает это за вас.
- Следующим шагом является передача всех свойств состояния компонента оболочки и всех действий, которые были инкапсулированы в функции на предыдущем шаге, в сущность компонента с помощью метода props.
- Наконец, верните упакованный компонент.Теперь мы можем использовать метод this.props.username, чтобы получить статус в магазине внутри сущности компонента, или использовать метод this.props.userinfoActions.login(data) для отправки действий, в это время, логика компонента, работающего с хранилищем, отделена от логики самого компонента, а внутренние сущности компонента могут использоваться повторно.
наконец
В этой статье представлен основной принцип реализации React-Redux, а также реализован простой и компактный react-redux путем инкапсуляции компонента Provider и метода подключения. Соответствующий код этой статьи размещен на github, вы можетеНажмите здесь, чтобы просмотреть, Если вы думаете, что это хорошо, добро пожаловать на главную роль, эта серия время от времени обновляется, добро пожаловать на внимание ~