手挽手帶你學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的實現和寫法,大家自己去實現真正的寫法,這裡不做演示相當於給大家留個作業,有錯誤或者是有建議 或者有不懂的地方 掃碼加我微信給大家解答。