React个人入门总结《五》
简介
这一次总结的是 React-redux 的实现,可以参考一下 大佬的文章 。
首先要知道
redux
的基本使用:
创建一个
Store
。<!-- store --> function createStore (reducer) {let state = nullconst listeners = []const subscribe = (listener) => listeners.push(listener)const getState = () => stateconst dispatch = (action) => {state = reducer(state, action)listeners.forEach((listener) => listener())}dispatch({}) // 初始化 statereturn { getState, dispatch, subscribe } }<!-- reducer --> const themeReducer = (state = {}, action) => {switch (action.type) {case 'CHANGE_COLOR':return { ...state, themeColor: action.themeColor }default:return state} }<!-- 创建 store --> const store = createStore(themeReducer) 复制代码
Store
是保存数据的地方,整个应用只有一个,调用CreateStore
函数并且传入一个Reducer
来创建一个Store
,并且会返回新的Store
对象。获取当前的
State
。<!-- 调用 store.getState 获取当前的状态 --> const state = store.getState() 复制代码
State
是Store
里面包含的数据对象,可以通过Store.getState()
获取通过
Dispatch
发送Action
改变State
。<!-- 调用 dispatch 发送 action --> store.dispatch({type: 'CHANGE_COLOR',themeColor: 'blue' }) 复制代码
Action
就是View
发出的通知,表示View
要变化,其中Type
是必须的,其余可以 自定义 。如果要写多个
Action
觉得麻烦,可以使用Action Creator
函数来生产Action
。function updateThemeColor (action) {type: action.type,themeColor: action.themeColor }store.dispatch( updateThemeColor({ type: 'CHANGE_COLOR', themeColor: 'blue' }) ) 复制代码
Reducer
是Store
收到Action
之后用来计算State
并且返回新的State
,也就是说必须要有Return
。<!-- reducer --><!-- 初始 state 是必须的,redux 规定不能为 undefined 和 null --> const themeReducer = (state = {}, action) => {switch (action.type) {case 'CHANGE_COLOR':return { ...state, themeColor: action.themeColor }default:return state} } 复制代码
Reducer
可以根据不同的Type
来进行不同的逻辑处理,并且每次都会返回新的state
来覆盖原来的state
。Reducer
是一个纯函数,同样的输入就会得到同样的输出。Reducer
必须要返回一个新的状态,而不是改变原有的状态,请参考下面写法:// State 是一个对象 function reducer(state, action) {return Object.assign({}, state, { thingToChange });// 或者return { ...state, ...newState }; }// State 是一个数组 function reducer(state, action) {return [...state, newItem]; } 复制代码
调用
subscribe
传入一个函数,状态改变时会调用此函数。store.subscribe(()=> {ReactDOM.render() }) 复制代码
Store.subscribe
方法设置监听函数,一旦State
发生变化,就自动执行这个函数。一般传入
render
和this.setState()
来监听页面重新渲染。调用此方法会返回一个函数,调用函数之后可以解除监听。
React-redux
首先之前向组件传递参数时,第一次使用的是 状态提升,即通过父级传入一个函数然后拿到组件里面的东西,再传入另一个组件里面。
当嵌套的太多层时,使用 状态提升 会非常麻烦,然后第二次就开始使用了 Context ,由于 Context 能随意被改变,这时我们可以把 Context 和 Store 结合使用,这样就不能随意的改变 Context ,并且状态还能共享。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
// 引入组件
import { Header } from './component/Header';
import { Content } from './component/Content';
import * as serviceWorker from './serviceWorker';
<!-- store -->
function createStore(reducer) {let state = null;const listeners = [];const subscribe = (listener) => listeners.push(listener);const getState = () => state;const dispatch = (action) => {state = reducer(state, action);listeners.forEach((listener) => listener());}dispatch({});return { getState, dispatch, subscribe };
}
<!-- reducer -->
const themeReducer = (state, action) => {if (!state) return {themeColor: 'red'}switch (action.type) {case 'CHANGE_COLOR':return { ...state, themeColor: action.themeColor }default:return state}
}
<!-- 创建 store -->
const store = createStore(themeReducer);class Index extends Component {<!-- 设置子组件的 contextType -->static childContextTypes = {store: PropTypes.object}<!-- 设置 context -->getChildContext() {return { store }}render() {return (<div className="index"><Header /><Content /></div>)}
}ReactDOM.render(<Index />, document.getElementById('root'));
复制代码
创建 store 然后把它放到 context 里面,这样所有子组件都可以拿到了。
<!-- Header -->
import React, { Component } from 'react';
import PropTypes from 'prop-types';class Header extends Component {static contextTypes = {store: PropTypes.object }constructor(props) {super(props);this.state = {themeColor: ''}}componentWillMount() {let { store } = this.context;this._updateThemeColor();store.subscribe(() => this._updateThemeColor())}_updateThemeColor() {let state = this.context.store.getState();this.setState({themeColor: state.themeColor})}render() {return (<div className="header"><h1 style={{color: this.state.themeColor}}>is header</h1></div>)}
}
export { Header };<!-- Content-->
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { ThemeSwitch } from './ThemeSwitch';class Content extends Component {static contextTypes = {store: PropTypes.object}componentWillMount() {let { store } = this.context;this._updateThemeColor();store.subscribe(() => this._updateThemeColor())}_updateThemeColor() {let state = this.context.store.getState();this.setState({themeColor: state.themeColor})}render() {return (<div className="header"><h2 style={{ color: this.state.themeColor }}>is Content</h2><ThemeSwitch /></div>)}
}
export { Content };<!-- ThemeSwitch -->
import React, { Component } from 'react';
import PropTypes from 'prop-types';class ThemeSwitch extends Component {static contextTypes = {store: PropTypes.object}componentWillMount() {let { store } = this.context;this._updateThemeColor();store.subscribe(() => this._updateThemeColor())}_updateThemeColor() {let state = this.context.store.getState();this.setState({themeColor: state.themeColor})}render() {return (<div className="header"><button style={{ color: this.state.themeColor }}>red</button><button style={{ color: this.state.themeColor }}>blue</button></div>)}
}export { ThemeSwitch };
复制代码
上面三个子组件使用 store.getState() 获取到 reducer 设置的默认状态,这样的话就可以实现共享状态了。
接下来我们实现点击按钮改变颜色:
<!-- ThemeSwitch -->
updateThemeColor(color) {let { store } = this.context;store.dispatch({type: 'CHANGE_COLOR',themeColor: color})
}render() {return (<div className="header"><button style={{ color: this.state.themeColor }} onClick={this.updateThemeColor.bind(this, 'red')}>red</button><button style={{ color: this.state.themeColor }} onClick={this.updateThemeColor.bind(this, 'blue')}>blue</button></div>)
}
复制代码
调用 dispatch 然后传入 action ,然后会调用 reducer 函数,然后根据传入的 action.type 改变状态,之后再返回一个新的状态。
返回新状态时要想监听页面的更新,可以在 subscribe 传入要监听的函数,这样就可以在调用 dispatch 同时会调用你传入的函数,然后再一次调用 this.setState 触发页面重新渲染。
_updateThemeColor() {<!-- 重新获取一次状态 -->let state = this.context.store.getState();<!-- 重新设置,并且触发重新渲染 -->this.setState({themeColor: state.themeColor})}componentWillMount() {let { store } = this.context;<!-- 首次渲染 -->this._updateThemeColor();store.subscribe(() => this._updateThemeColor())}
复制代码
connect
上面的组件有着重复的逻辑,首先取出 store 的 state 然后设置成自己的状态,还有一个就是对 context 依赖过强,这时我们可以利用 高阶组件 来和 context 打交道,这时就不用每个组件都获取一遍 store 了。
import React, { Component } from 'react';
import PropTypes from 'prop-types';
<!-- 接受一个组件 -->
const connect = (WrappedComponent) => {class Connect extends Component {static contextTypes = {store: PropTypes.object}render() {const { store } = this.context;return <WrappedComponent />}}return Connect;
}
export { connect };
复制代码
connect 是用于从 UI 组件生成 容器组件 ,也就是说我们传入的组件只是负责呈现和展示,而 容器组件 负责业务逻辑和带有内部状态,connect 负责的是将两者合并起来,生成并返回新的组件。
由于每个传进去的组件需要的 store 里面的数据都不一样,所以我们还要传入一个函数来告诉 高阶组件 正确获取数据。
- mapStateToProps
const mapStateToProps = (state) {return {themeColor: state.themeColor}
}
复制代码
mapStateToProps 是一个获取 store 保存的状态,然后将这个状态转化为 UI组件 的数据的函数,它必须要返回一个对象,而这个对象用来进行状态转化的。
import React, { Component } from 'react';
import PropTypes from 'prop-types';const connect = (mapStateToProps) => (WrappedComponent) => {class Connect extends Component {static contextTypes = {store: PropTypes.object}render () {const { store } = this.context;<!-- 从store获取state并且转化之后全部传入 props -->let stateProps = mapStateToProps(store.getState());return <WrappedComponent {...stateProps} />}}return Connect;
}export { connect };
复制代码
上面最关键的一步就是调用 mapStateToProps 时,从 store 获取到 state 之后然后传入到 mapStateToProps 函数中,然后这函数会返回一个转化后的 state ,然后把这些转化的状态全部传入 props 里面。
可以看出 connect 把 Dumb组件(纯组件) 和 context 连起来了,下面只需要调用 connect 然后传入一个 mapStateToProps 和 UI组件 就可以使用了。
<!-- Header -->
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from './React-redux';class Header extends Component {static propTypes = {themeColor: PropTypes.string}render() {return (<div className="header"><h1 style={{color: this.props.themeColor}}>is header</h1></div>)}
} const mapStateToProps = (state) => {return {themeColor: state.themeColor}
}Header = connect(mapStateToProps)(Header)
export { Header };<!-- Content -->
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from './React-redux';
import { ThemeSwitch } from './ThemeSwitch';class Content extends Component {static propTypes = {themeColor: PropTypes.string}render() {return (<div className="header"><h2 style={{ color: this.props.themeColor }}>is Content</h2><ThemeSwitch /></div>)}
}const mapStateToProps = (state) => {return {themeColor: state.themeColor}
}Content = connect(mapStateToProps)(Content)
export { Content };
复制代码
由于 mapStateToProps 返回的对象经过 connect 传入组件的 props 中,我们直接可以用 this.props 直接获取到。
接着把 connect 的代码复制到一个叫 React-redux 的文件,然后可以删掉之前那些引入 store 的代码了。
现在点击按钮只有按钮会变颜色,接下来我们修改一下 connect :
import React, { Component } from 'react';
import PropTypes from 'prop-types';const connect = (mapStateToProps) => (WrappedComponent) => {class Connect extends Component {static contextTypes = {store: PropTypes.object}constructor (props) {super(props);this.state = { allProps: {}}}componentWillMount () {const { store } = this.context;this._updateProps();store.subscribe(() => this._updateProps())}_updateProps () {const { store } = this.context<!-- 现在 mapStateToProps 可以接受两个参数 -->let stateProps = mapStateToProps(store.getState(), this.props)<!-- 整合普通的 props 和从 state 生成的 props -->this.setState({allProps: {...stateProps,...this.props}})}render () {return <WrappedComponent { ...this.state.allProps } />}}return Connect;
}
export { connect };
复制代码
每次点击按钮调用 dispatch 都会把心的 state 设置到自己的 state 之后,然后返回给组件,这样组件之前的 props 也会保留,同时 mapStateToProps 可以接受第二个参数,这个参数为当前 UI组件 的 props 。
<!-- 第一个为 store 获取到的 state , 第二个为当前 ui 组件的 props (不是最新的) -->
const mapStateToProps = (state, props) => {console.log(state, props)return {themeColor: state.themeColor}
}
复制代码
使用 props 作为参数后,如果容器组件的参数发生变化,也会引发 UI组件 重新渲染,connect 方法可以省略 mapStateToProps 参数,这样 store 的更新不会引起组件的更新。
- mapDispatchToProps
const mapDispatchToProps = (dispatch, props) => {return {updateThemeColor: () => {dispatch({type: 'CHANGE_COLOR',payload: ''})}}
}
复制代码
mapDispatchToProps 是 connect 的第二个参数,用来建立 UI组件 的参数到 store.dispatch 方法的映射,它作为函数时可以接受两个参数,一个是 dispatch ,一个则是 UI组件 的 props 。
mapDispatchToProps 可以定义 action 然后传给 store 。
import React, { Component } from 'react';
import PropTypes from 'prop-types';const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {class Connect extends Component {static contextTypes = {store: PropTypes.object}constructor (props) {super(props);this.state = { allProps: {}}}componentWillMount () {const { store } = this.context;this._updateProps();store.subscribe(() => this._updateProps())}_updateProps () {const { store } = this.contextlet stateProps = mapStateToProps? mapStateToProps(store.getState(), this.props): {}let dispatchProps = mapDispatchToProps? mapDispatchToProps(store.dispatch, this.props): {}this.setState({allProps: {...stateProps,...dispatchProps,...this.props}})}render () {return <WrappedComponent { ...this.state.allProps } />}}return Connect;
}
export { connect };
复制代码
接受 mapDispatchToProps 作第二个参数,调用时把 dispatch 和 props 传进去,返回 onClickUpdate 然后直接传入 props 中返回给 UI组件 ,接着我们可以直接调用 this.props.onClickUpdate 然后调用 dispatch 来更新状态。
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from './React-redux';class ThemeSwitch extends Component {static contextTypes = {onClickUpdate: PropTypes.func}<!-- 点击调用 onClickUpdate -->updateThemeColor(color) {if(this.props.onClickUpdate) {this.props.onClickUpdate(color)}}render() {return (<div className="header"> <button style={{ color: this.props.themeColor }} onClick={this.updateThemeColor.bind(this, 'red')}>red</button><button style={{ color: this.props.themeColor }} onClick={this.updateThemeColor.bind(this, 'blue')}>blue</button></div>)}
}
<!-- 在真正的 react-redux 不一定是函数 -->
const mapStateToProps = (state, props) => {return {themeColor: state.themeColor}
}
<!-- 在真正的 react-redux 可以是一个对象 -->
const mapDispatchToProps = (dispatch, props) => {return {onClickUpdate: (color) => {dispatch({type: 'CHANGE_COLOR',themeColor: color})}}
}ThemeSwitch = connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch);
export { ThemeSwitch };
复制代码
这样点击按钮之后又可以改变颜色了。
Provider
connect 方法生成容器后需要拿到 state 对象,目前咱们能拿到 store 是因为在
index.js
中设置了 context ,这样会直接污染index.js
, React-redux 提供了 Provider 来充当最外层容器,这样就不需要在index
设置 context 了。
class Provider extends Component {static propTypes = {store: PropTypes.object,children: PropTypes.any}static childContextTypes = {store: PropTypes.object}getChildContext () {return {store: this.props.store}}render () {return (<div>{this.props.children}</div>)}
}export { Provider };
复制代码
在 React-redux 的文件增加上面的代码,其实也就是另外设置一个容器来替代之前
index.js
干的活,这里返回了 this.props.children ,也说明要用这个组件把其他的组件包起来。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import { Provider } from './component/React-redux';
// 引入组件
import { Header } from './component/Header';
import { Content } from './component/Content';
import * as serviceWorker from './serviceWorker';function createStore(reducer) {let state = null;const listeners = [];const subscribe = (listener) => listeners.push(listener);const getState = () => state;const dispatch = (action) => {state = reducer(state, action);listeners.forEach((listener) => listener());}dispatch({});return { getState, dispatch, subscribe };
}const themeReducer = (state, action) => {if (!state) return {themeColor: 'red'}switch (action.type) {case 'CHANGE_COLOR':return { ...state, themeColor: action.themeColor }default:return state}
}const store = createStore(themeReducer);class Index extends Component {render() {return (<div className="index"><Header /><Content /></div>)}
}ReactDOM.render(<!-- 把 store 和 外层组件包起来 --><Provider store= { store }><Index /></Provider>, document.getElementById('root')
);
复制代码
把 Store 传入给 Provider ,然后它把 store 设置成 context ,这样其他子组件都能拿到 store ,并且把最外层容器包起来,然后使用 this.props.children 全部罗列出来。
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from './React-redux';class Header extends Component {<!-- 别忘了声明这玩意,不然拿不到 -->static contextTypes = {store: PropTypes.object}static propTypes = {themeColor: PropTypes.string}componentWillMount() {console.log(this.context)}render() {return (<div className="header"><h1 style={{color: this.props.themeColor}}>is header</h1></div>)}
} const mapStateToProps = (state, props) => {return {themeColor: state.themeColor}
}Header = connect(mapStateToProps)(Header)
export { Header };
复制代码
拿到之后接下来就可以浪了,可以在当前组件调用里面的方法,非常灵活。
总结
- 首先在
index.js
引入创建好的store
,然后引入Provider
把index
包起来,并且给它传递store
。 - 如果页面需要拿到状态直接调用
store.getState
,如果想监听函数调用store.subscribe
传入函数。 - 如果想订阅
store
或者修改state
,在当前组件引入connect
接着传入mapStateToProps
和mapDispatchToProps
来呈现新的UI组件
。 dispatch
还可以拓展,真正的react-redux
还可以使用中间件实现异步action
,如需要从后台返回的状态来改变当前的state
类似这种操作。- 可以使用
combineReducers
管理多个reducer
,一个store
管理N种状态。
上一篇 --- React个人入门总结《四》
下一篇 --- React个人入门总结《六》
React个人入门总结《五》相关推荐
- React.js入门笔记
# React.js入门笔记 核心提示 这是本人学习react.js的第一篇入门笔记,估计也会是该系列涵盖内容最多的笔记,主要内容来自英文官方文档的快速上手部分和阮一峰博客教程.当然,还有我自己尝试的 ...
- web前端高级React - React从入门到进阶之React条件渲染
系列文章目录 第一章:React从入门到进阶之初识React 第一章:React从入门到进阶之JSX简介 第三章:React从入门到进阶之元素渲染 第四章:React从入门到进阶之JSX虚拟DOM渲染 ...
- web前端高级React - React从入门到进阶之初识React
第一部分:React入门 系列文章目录 第一章:React从入门到进阶之初识React 第一章:React从入门到进阶之JSX简介 第三章:React从入门到进阶之元素渲染 第四章:React从入门到 ...
- web前端高级React - React从入门到进阶之高阶组件
第二部分:React进阶 系列文章目录 第一章:React从入门到进阶之初识React 第一章:React从入门到进阶之JSX简介 第三章:React从入门到进阶之元素渲染 第四章:React从入门到 ...
- web前端高级React - React从入门到进阶之组件的状态提升
系列文章目录 第一章:React从入门到进阶之初识React 第一章:React从入门到进阶之JSX简介 第三章:React从入门到进阶之元素渲染 第四章:React从入门到进阶之JSX虚拟DOM渲染 ...
- web前端高级React - React从入门到进阶之组件的懒加载及上下文Context
第二部分:React进阶 系列文章目录 第一章:React从入门到进阶之初识React 第一章:React从入门到进阶之JSX简介 第三章:React从入门到进阶之元素渲染 第四章:React从入门到 ...
- web前端高级React - React从入门到进阶之Render Props
第二部分:React进阶 系列文章目录 第一章:React从入门到进阶之初识React 第一章:React从入门到进阶之JSX简介 第三章:React从入门到进阶之元素渲染 第四章:React从入门到 ...
- Python3快速入门(五)——Python3函数
Python3快速入门(五)--Python3函数 一.函数定义 1.函数定义 Python 定义函数使用 def 关键字,一般格式如下: def function_name(parameter_li ...
- React学习:入门实例-学习笔记
文章目录 React学习:入门实例-学习笔记 React的核心 需要引入三个库 什么是JSX react渲染三步骤 React学习:入门实例-学习笔记 React的核心 1.组件化:把整一个网页的拆分 ...
最新文章
- C#实现Web应用程序定时启动任务
- 无需VR外设,普林斯顿学霸用DeepHand解放你的双手
- 如何预约升级鸿蒙,超过66万人预约,华为亮出真正王牌旗舰,支持优先升级鸿蒙系统...
- [Hadoop in China 2011] 何鹏:Hadoop在海量网页搜索中应用分析
- 查看nginx php mysql apache编译安装参数
- php中abs,php中的abs函数怎么用
- 作业2-Python基础练习
- python之numpy基础_Python之Numpy操作基础
- 服务器安装系统提示加载驱动程序,解决安装win7的提示“加载驱动程序”的问题...
- 右键txt打开html,文件解压不了怎么办 右键菜单中选择解压文件
- FastDFS V6.06 阿里云集群安装配置双IP(踩坑)
- ObjectARX开发笔记(一)——分别使用AcEdInputPointFilter和AcEdInputPointMonitor实现光标提示功能
- 第一次投稿(Elsevier)爱斯维尔期刊经验(持续更新)
- css中创建主轴方向,flex-direction
- linux smit工具,AIX smit工具
- svg背景_SVG电影背景:安迪的房间,俯瞰酒店
- 关于Editable的学习
- 海康工业相机SDK+OpenCV实例(2):RawDataFormatConvert详解
- hp服务器性能下降,MS SQL Server2000 运行在HP刀片服务器上性能下降,如何解决
- 2345浏览器的2.4版本,在2013年1月19日,像小丑一样笑着!
热门文章
- C++ 用libcurl库进行http 网络通讯编程
- Python数据存储:pickle模块的使用讲解(测试代码)
- HDU-Largest Rectangle in a Histogram-1506 单调栈
- poj3253 Fence Repair(贪心+哈夫曼 经典)
- ASP.NET MVC4 部分视图
- HDU 4336 概率DP 状压
- 读书笔记_代码大全_第14章_组织直线型代码_第15章_使用条件语句
- 多Kinect下WaitNoneUpdateAll老是报错,烦躁……
- rabbitmq 学习-2-安装
- 【android】读取/res/raw目录下的文件