手挽手带你学 React 入门四档, 用人话教你 react-redux,理解 redux 架构,以及运用在 react 中。学完这一章,你就可以开始自己的 react 项目了。
视频教程
上一篇我们自己实现了 Redux,这一篇我们来看看如何去实现一个 react-redux
开始之前
本文需要用到的知识
首先你要会 React 的基础(这是废话)高阶组件 React context 满足这三项我们开始往下看。
react 结合 redux
搭建基础环境
我们上一章讲过了 redux 的原理,内部是有一个 store 的,store 只有 dispatch 才可以控制它变化。还有在文章一开始我们讲了 React context 可以跨组件传递数据,那么到现在你想到把我们的 store 挂到最外层一个组件的 context 上了吗?话不多说 开始!
我们先修改一下我们的 react 文件(注意不是 redux.html 这个文件了)
// App.js
import React,{Component} from ‘react’
import PropTypes from ‘prop-types’ // 引入
export default class App extends Component {
constructor(){
super()
this.state={
}
}
componentWillMount(){
// console.log(hashHistory)
}
render() {
return (
<div>
<Children />
<ChildrenTwo />
</div>
)
}
}
// 为了展示效果定义子组件一
class Children extends Component{
constructor(){
super()
this.state={
}
}
render(){
return(
<div>
<h1> 我是脑袋 </h1>
<h2> 我是身体 </h2>
</div>
)
}
}
// 为了展示效果定义子组件二 ChildrenTwo 是 Children 的子组件 但是却通过 context 拿到了 App 组件拿过来的值(越级传递)
class ChildrenTwo extends Component{
constructor(){
super()
this.state={
}
}
render(){
return(
<div>
<button> 变字 </button>
<button> 变色 </button>
</div>
)
}
}
创建基本 store
现在我们做好了示例文件的基础模板了,然后我们需要创建一个 store.js
// store.js
// 这是我们的 reducer
const changeDom = (state,action) => {
if(!state)return{
text : “ 我是实例文字 ”,
color : ‘red’
}
switch(action.type){
case “CHANGE_TEXT”:
return{
…state,
text:action.text
}
case “CHANGE_COLOR”:
return{
…state,
color:action.color
}
default:
return state
}
}
const creatStore = (reducer)=>{
let state = null
const listeners = []
const subscribe = (liestner)=>listeners.push(liestner)
const getState = ()=>state
const dispatch=(action)=>{
state = reducer(state,action)
listeners.map(item=>item())
}
dispatch({})
return {getState, dispatch, subscribe}
}
export {creatStore,changeDom}
结合 context 使用 store
我们现在把我们的子组件通过 context 公用 App 的 store, 并且把渲染方法和 dispatch 应用进去
// App.js
import React,{Component} from ‘react’
import PropTypes from ‘prop-types’ // 引入
// 引入 store
import {changeDom,creatStore} from ‘./store’
const store = creatStore(changeDom)
export default class App extends Component {
// 我们在这里把 store 挂到 context 上
static childContextTypes = {
store: PropTypes.object
}
getChildContext(){
return{store}
}
constructor(){
super()
this.state={
}
}
componentWillMount(){
// console.log(hashHistory)
}
render() {
return (
<div>
<Children />
<ChildrenTwo />
</div>
)
}
}
// 这里我们再去修改我们的子孙组建 让他们可以拿到 store
// 为了展示效果定义子组件一
class Children extends Component{
static contextTypes = {
store: PropTypes.object
}
constructor(){
super()
this.state={
color:””,
text:””
}
}
// 这里我们定义两个渲染方法
getColor(){
let store = this.context.store.getState()
this.setState({color:store.color})
}
// 这里我们定义两个渲染方法
getText(){
let store = this.context.store.getState()
this.setState({text:store.text})
}
// 这里组件加载之前渲染
componentWillMount(){
this.getColor()
this.getText()
}
render(){
return(
<div>
<h1 style={{color:this.state.color}}>{this.state.text}</h1>
</div>
)
}
}
// 为了展示效果定义子组件二 ChildrenTwo 是 Children 的子组件 但是却通过 context 拿到了 App 组件拿过来的值(越级传递)
class ChildrenTwo extends Component{
static contextTypes = {
store: PropTypes.object
}
constructor(){
super()
this.state={
}
}
// 这里我们定义 两个 action
changeColor=()=>{
this.context.store.dispatch({type:”CHANGE_COLOR”,color:”green”})
console.log(this.context.store.getState())
}
changeText=()=>{
this.context.store.dispatch({type:”CHANGE_TEXT”,text:” 我变了 ”})
console.log(this.context.store.getState()) // 这里方便大家看到数据变了
}
render(){
return(
<div>
<button onClick={()=>{this.changeText()}}> 变字 </button>
<button onClick={()=>{this.changeColor()}}> 变色 </button>
</div>
)
}
}
显然 现在我们点击按钮的时候,并没有发生任何事情,可是我们看 console 可以看到,store 的数据确实变化了,这是为什么呢?很简单 我们没有把 dom 监听放进来,下一步我们要设置监听。
设置 dom 监听
// Children 组件 我们在 componentWillMount 中添加监听
class Children extends Component{
static contextTypes = {
store: PropTypes.object
}
constructor(){
super()
this.state={
color:””,
text:””
}
}
// 这里我们定义两个渲染方法
getColor(){
let store = this.context.store.getState()
this.setState({color:store.color})
}
// 这里我们定义两个渲染方法
getText(){
let store = this.context.store.getState()
this.setState({text:store.text})
}
// 这里组件加载之前渲染
componentWillMount(){
this.getColor()
this.getText()
this.context.store.subscribe(()=>{this.getColor()})
this.context.store.subscribe(()=>{this.getText()})// 使用箭头函数 保证 this 指向
}
render(){
return(
<div>
<h1 style={{color:this.state.color}}>{this.state.text}</h1>
</div>
)
}
}
到这里我们已经简单地实现了 react-redux,可是大家有没有觉得怪怪的?
开始创建 connect
没错 到这里我们频繁使用 context, 并且每个组件都要去手动挂 context 这是相当麻烦的,现在我们想要让这些东西都自动包裹 自动生成,我们再怎么做呢。我们创建一个高阶组件就可以搞定这个问题了(一个函数,接收一个组件,生成另外一个包装好的新组件)
import React, {Component} from ‘react’
import PropTypes from ‘prop-types’
export const connect = (WrappedComponent)=>{
class Connect extends Component{
static contextTypes = {
store: PropTypes.object
}
render(){
return<WrappedComponent />
}
}
return Connect
}
我们这里通过 connect 函数来接收一个组件 经过一层封装 返回一个包装了 context 的组件 但是现在这样的话 我们每次使用都还需要大量的书写 this.context 是不是相当恶心呢?并且每个组件都拿到了 store 有很多事它并不需要的,这时候我们就需要告诉这个高阶组件,我这里就只需要这些东西。这就是 mapStateToProps
import React, {Component} from ‘react’
import PropTypes from ‘prop-types’
// 示例 这里不用
// const mapStateToProps=(state)=>{
// return{
// color:state.color,
// text:state.text,
// }
// } 就是我们需要从 store 拿出来的东西
const connect= (mapStateToProps) => (WrappedComponent)=>{
class Connect extends Component{
static contextTypes = {
store: PropTypes.object
}
render(){
const store = this.context.stote
let stateProps = mapStateToProps(store.getState())
然后我们把 stateProps 用 … 展开
return<WrappedComponent {…stateProps}/>
}
}
return Connect
}
现在我们利用 Connect 改造我们的组件
// App.js
import React,{Component} from ‘react’
import PropTypes from ‘prop-types’ // 引入
// 引入 store
import {changeDom,creatStore} from ‘./store’
const store = creatStore(changeDom)
// ________________________________把 connect_____________________________写在 app.js 为的是不引入了 实际上它是单独拆分在 react-redux 中的
const connect = (mapStateToProps) => (WrappedComponent)=>{
class Connect extends Component{
static contextTypes = {
store: PropTypes.object
}
render(){
const store = this.context.store
console.log(store)
let stateProps = mapStateToProps(store.getState())
// 然后我们把 stateProps 用 … 展开
return<WrappedComponent {…stateProps}/>
}
}
return Connect
}
// ________________________________把 connect_____________________________写在 app.js 为的是不引入了 实际上它是单独拆分在 react-redux 中的
// app.js 的其他内容 …
// ________________________________对组件一进行修改_____________________________
class Children extends Component{
constructor(){
super()
this.state={
color:””,
text:””
}
}
render(){
return(
<div>
<h1 style={{color:this.props.color}}>{this.props.text}</h1>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
color: state.color,
text:state.text
}
}
Children = connect(mapStateToProps)(Children)
// ________________________________对组件一进行修改_____________________________
现在我们成功把 store 里面的所有东西都挂到了 props 上面,我们不需要去依赖 context,只需要调用 props 即可。剩下的就是我们的数据变更,渲染还有监听了!
对 connect 进一步改造 其实就是像我们上面的组件一样 挂载监听
const connect = (mapStateToProps) => (WrappedComponent)=>{
class Connect extends Component{
static contextTypes = {
store: PropTypes.object
}
componentWillMount(){
const{store} = this.context
this.updata()
store.subscribe(()=>this.updata())
}
updata=()=>{
const {store} = this.context
let stateProps = mapStateToProps(store.getState(), this.props)
this.setState({
allProps: {// 整合普通的 props 和从 state 生成的 props
…stateProps,
…this.props
}
})
}
render(){
// 然后我们把 allProps 用 … 展开
return<WrappedComponent {…this.state.allProps}/>
}
}
return Connect
}
mapDispatchToProps
state 我们改造完了,dispatch 是不是也要一起改造呢?答案是肯定的,mapDispatchToProps 就是做这个的。我们看看 mapDispatchToProps 是怎么写的 我们倒着推
//const mapDispatchToProps = (dispatch) => {// 传入的是 dispatch
// return {// 返回一个函数
// changeColor: (color) => {
// dispatch({type: ‘CHANGE_COLOR’, color: color})
// }
// }
//}
const connect = (mapStateToProps,mapDispatchToProps) => (WrappedComponent)=>{
class Connect extends Component{
static contextTypes = {
store: PropTypes.object
}
componentWillMount(){
const{store} = this.context
this.updata()
console.log(store)
store.subscribe(()=>this.updata())
}
updata=()=>{
const {store} = this.context
let stateProps = mapStateToProps?mapStateToProps(store.getState(), this.props):{}
let dispatchProps = mapDispatchToProps?mapDispatchToProps(store.dispatch,this.props):{}
// 我们要考虑到空值处理
this.setState({
allProps: {// 整合普通的 props 和从 state 生成的 props
…stateProps,
…this.props
…dispatchProps,
}
})
}
render(){
// 然后我们把 allProps 用 … 展开
return<WrappedComponent {…this.state.allProps}/>
}
}
return Connect
}
到这里我们可以把 ChildrenTwo 的代码也改造了
class ChildrenTwo extends Component{
constructor(){
super()
this.state={
}
}
render(){
console.log(this.props)
return(
<div>
<button onClick={()=>{this.props.changeText(“ 我变了 ”)}}> 变字 </button>
<button onClick={()=>{this.props.changeColor(“green”)}}> 变色 </button>
</div>
)
}
}
const mapDispatchToProps = (dispatch)=>{
return {// 返回一个函数
changeColor: (color) => {
dispatch({type: ‘CHANGE_COLOR’, color: color})
},
changeText:(text)=>{
dispatch({type: ‘CHANGE_TEXT’, text: text})
}
}
}
ChildrenTwo = connect(null,mapDispatchToProps)(ChildrenTwo)
到现在 一个简单的 react-redux 已经差不多了,但是 react-redux 里面还有一个 <Provider> 但是我们还没有这东西 这是什么呢?实际上它做的事跟我们 App 这个组件做的事一毛一样,主要就是把 store 挂到 context 上。
export class Provider extends Component{
static childContextTypes = {
store: PropTypes.object
}
getChildContext(){
return{store}
}
render() {
return (
<div>{this.props.children}</div>
)
}
}
于是 我们现在的代码变成了这样
好了 道理讲完了 我们现在开始拆分代码啦。
// store.js
export const creatStore = (reducer)=>{
let state = null
const listeners = []
const subscribe = (liestner)=>listeners.push(liestner)
const getState = ()=>state
const dispatch=(action)=>{
state = reducer(state,action)
listeners.map(item=>item())
}
dispatch({})
return {getState, dispatch, subscribe}
}
// reducer.js
// 这是我们的 reducer 可以单独拆分成一个 js 文件 自己拆吧
export const changeDom = (state,action) => {
if(!state)return{
text : “ 我是实例文字 ”,
color : ‘red’
}
switch(action.type){
case “CHANGE_TEXT”:
return{
…state,
text:action.text
}
case “CHANGE_COLOR”:
return{
…state,
color:action.color
}
default:
return state
}
}
// react-redux.js
import React,{Component} from ‘react’
import PropTypes from ‘prop-types’ // 引入
export const connect = (mapStateToProps,mapDispatchToProps) => (WrappedComponent)=>{
class Connect extends Component{
static contextTypes = {
store: PropTypes.object
}
componentWillMount(){
const{store} = this.context
this.updata()
store.subscribe(()=>this.updata())
}
updata=()=>{
const {store} = this.context
let stateProps = mapStateToProps?mapStateToProps(store.getState(), this.props):{}
let dispatchProps = mapDispatchToProps?mapDispatchToProps(store.dispatch,this.props):{}
// 做一下空值处理
this.setState({
allProps: {// 整合普通的 props 和从 state 生成的 props
…stateProps,
…this.props,
…dispatchProps,
}
})
}
render(){
// 然后我们把 allProps 用 … 展开
return<WrappedComponent {…this.state.allProps}/>
}
}
return Connect
}
export class Provider extends Component{
static childContextTypes = {
store: PropTypes.object
}
getChildContext(){
return {store:this.props.store}
}
render() {
return (
<div>{this.props.children}</div>
)
}
}
// App.js
import React,{Component} from ‘react’
import Children from ‘./Children’
import ChildrenTwo from ‘./ChildrenTwo’
export default class App extends Component {
constructor(){
super()
this.state={
}
}
render() {
return (
<div>
<Children />
<ChildrenTwo />
</div>
)
}
}
// Children.js
import React,{Component} from ‘react’
import{connect} from ‘./react-redux.js’
class Children extends Component{
constructor(){
super()
this.state={
color:””,
text:””
}
}
render(){
return(
<div>
<h1 style={{color:this.props.color}}>{this.props.text}</h1>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
color: state.color,
text:state.text
}
}
Children = connect(mapStateToProps)(Children)
export default Children
// ChildrenTwo.js
import React,{Component} from ‘react’
import{connect} from ‘./react-redux.js’
class ChildrenTwo extends Component{
constructor(){
super()
this.state={
}
}
render(){
return(
<div>
<button onClick={()=>{this.props.changeText(“ 我变了 ”)}}> 变字 </button>
<button onClick={()=>{this.props.changeColor(“green”)}}> 变色 </button>
</div>
)
}
}
const mapDispatchToProps = (dispatch)=>{
return {// 返回一个函数
changeColor: (color) => {
dispatch({type: ‘CHANGE_COLOR’, color: color})
},
changeText:(text)=>{
dispatch({type: ‘CHANGE_TEXT’, text: text})
}
}
}
ChildrenTwo = connect(null,mapDispatchToProps)(ChildrenTwo)
export default ChildrenTwo
拆完了的代码是不是简单明了真正工作里面 我们需要多做两步
npm i redux –save
npm i react-redux –save
然后 我们 对照着修改这几个位置
// creatStore 在 redux 插件中
// connect,Provider 在 react-redux 插件中
// 也就是用到哪里了 对应修改哪里 改完了你就发现了新大陆了
import {createStore} from ‘redux’
import {connect,Provider} from ‘react-redux’
不知不觉发现自己不仅仅会用了 react-redux 并且还自己实现了一个 react-redux 很舒坦的呢
总结
在我们四档下篇到这里结束了,这就是 react-redux 的实现和写法,大家自己去实现真正的写法,这里不做演示相当于给大家留个作业,有错误或者是有建议 或者有不懂的地方 扫码加我微信给大家解答。
视频制作中