【深入理解React】03_理解数据管理

前言

在React中数据的流通有多种方式,本文介绍了props、发布订阅模式、Context APIredux

props

props可以将父组件中的数据值传递给子组件,这个是单向传递的,即不允许子组件传值给父组件。基于props单向传递的特性,我们可以实现集中数据交流的模式:

父子组件之间的通信

使用props父组件将text值传递给子组件,子组件通过调用props.changeText的方法,改变父组件建中text的值,从而改变自身text的值。

// 父组件
import React from 'react'
import Child from './components/Children'

class App extends React.Component {
  state = {
    text: '初始化父组件的文本'
  }

  changeText = (text: string) => {
    this.setState({
      text
    })
  }

  render() {
    return (
      <div>
        <p>我是父组件</p>
        <Child text={ this.state.text }
          changeText={ this.changeText } />
      </div>
    )
  }
}

export default App

// 子组件
import React from 'react'

class Child extends React.Component<any, any> {

  handleChange = () => {
    this.props.changeText('我是子组件')
  }

  render() {
    return (
      <>
        <div>Child:{ this.props.text }</div>
        <button onClick={ this.handleChange }>点击改变props</button>
      </>
    )
  }
}

export default Child

改变前:

在这里插入图片描述

改变后:

在这里插入图片描述

兄弟组件通信

在这里插入图片描述

兄弟组件之间的通信可以化解为父子之间的通信,将值提升到父组件。

// 父组件
import React from 'react'
import Child from './components/Children'
import NewChild from './components/NewChildren'

class App extends React.Component {
  state = {
    text: '初始化父组件的文本'
  }

  changeText = (text: string) => {
    this.setState({
      text
    })
  }

  render() {
    return (
      <div>
        <p>我是父组件</p>
        <Child text={ this.state.text } />
        <NewChild changeText={ this.changeText } />
      </div>
    )
  }
}

export default App

// 子组件1
import React from 'react'

class Child extends React.Component<any, any> {

  render() {
    return (
      <div>Child:{ this.props.text }</div>
    )
  }
}

export default Child

// 子组件2
import React from 'react'

class Child extends React.Component<any, any> {

  handleChange = () => {
    this.props.changeText('NewChildren改变了Children的值')
  }

  render() {
    return (
      <button onClick={ this.handleChange }>点击改变children的值</button>
    )
  }
}

export default Child

这样点击子组件2的按钮,就可以改变子组件1的值了。

在这里插入图片描述

在这里插入图片描述

除此上述两种传递方式之外, props 并不适合处理其它复杂场景,例如任意层级组件间通信,层层传递 props 不可行,太难维护了

发布订阅模式

数据通信这件事我们可以联想到,其实就在某人发起了一个动作-我想要修改数据,某人收到了这个动作信号,更改了数据。有点类似于JS中的addEventListener()

 addEventListener('click', function(){});

当用户点击的时候,就会触发某个回调函数。接下来我们来写一个发布订阅的函数。

发布订阅有两个关键动作:事件监听(订阅)和事件触发(发布):

  • on:负责事件的监听,指定事件触发时的回调函数;
  • emit:负责触发事件,可以传参;
  • off:为了性能考虑,要移除一下监听器。
class myEventEmitter {
    constructor(){
        this.eventMap = {}
    }
    
    // 监听,每一种动作对应一个回调函数数组
    on(type, handler){
        if(typeof handler !== 'function'){
            throw new Error('handler must be function');
        }
        
        if(!this.eventMap[type]){
            this.eventMap[type] = [];
        }
        
        this.eventMap[type].push(handler);
    }
    
    // 触发,触发每一个动作对应的数组中的回调函数
    emit(type, params){
        if(this.eventMap[type]){
            this.eventMap[type].forEach(handler => {
                handler(params);
            })
        }
    }
    
    // 移除,移除对应的动作中对应的回调函数
    off(type, handler){
        if(this.eventMap[type]){
            this.eventMap[type].splice(this.eventMap[type].indexOf(handler) >>> 0, 1)
        }
    }
}

Context API

React 15的Context API由于有多种问题(比如如果中间某一个组件的shouldComponentUpdate返回了false,则该组件无法获得context的值),所以这里只学习React 16的Context API。

由于如果使用props一层一层传递属性的话,会相当麻烦,所以可以使用Context API创建一个全局的数据,给子组件共享,避免了通过中间元素传递props

  • 创建Context: const MyContext = React.createContext(defalutValue)

  • Context的值传递下去:<MyContext.Provider value={xxx};

  • 子组件使用Context中的数据: `

    <MyContext.Consumer>
    	{value => /* 基于 context 值进行渲染*/}
    </MyContext.Consumer>
    

栗子:

// context.ts
import { createContext } from 'react';

export const MyContext = createContext({} as any);
// 父组件
import React from 'react'
import Child from './components/Children'
import { MyContext } from './utils/Context'

class App extends React.Component {
  state = {
    text: '初始化父组件的文本'
  }

  changeText = (text: string) => {
    this.setState({
      text
    })
  }

  render() {
    return (
      // @ts-ignore
      <MyContext.Provider value={this.state} >
        <Child  />
      </MyContext.Provider>
    )
  }
}

export default App

// 第一个子组件
import React from 'react'
import SubChildren from './SubChildren'

class Child extends React.Component {

  render() {
    return (
      <>
        <SubChildren />
      </>
    )
  }
}

export default Child

// 第二个子组件(内嵌组件)
import React from 'react'
import { MyContext } from '../utils/Context'

class SubChildren extends React.Component<any, any> {

  render() {
    return (
      <div>
      <MyContext.Consumer>
        {(value: any) => `Hello:${value.text}`}
      </MyContext.Consumer>
      </div>
    )
  }
}

export default SubChildren

在这里插入图片描述

redux

用过vuex的应该也能理解redux,这里给一下redux的流程图:

在这里插入图片描述

用户通过界面组件 触发ActionCreator,携带Store中的旧State与Action 流向Reducer,Reducer返回新的state,并更新界面。

接下来说一下使用:

npm install --save redux
  • 创建actionType.ts: 存放actiontype的常量;
  • 创建action.ts: 返回不同的action
  • 创建reducer.ts: 根据action执行不同的操作;
  • 创建store.ts:初始化store,并绑定reducer.ts
// actionType.ts
export const ADDTEXT: string = 'ADDTEXT'
export const CHANGETEXT: string = 'CHANGETEXT'
// action.ts
import { ADDTEXT, CHANGETEXT } from './actionType'

export const addText = (text: string) => ({
  type: ADDTEXT,
  value: text
})

export const changeText = (text: string) => ({
  type: CHANGETEXT,
  value: text
})
// reducer.ts
import { ADDTEXT, CHANGETEXT } from './actionType'
const defaultState = {
  text: ''
}

function handleText(state = defaultState, action: {[key: string]: any}){
  switch(action.type){
    case ADDTEXT: 
      return { ...state, text: action.value };
    case CHANGETEXT: 
    return { ...state, text: action.value };
    default: 
      return state;
  }
}

export const finalReducer =  handleText
// store.ts
import { createStore } from 'redux'
import { finalReducer } from './reducer'

const store = createStore(finalReducer)

export default store
  • 我们在App.ts中给text新增一个值;
  • SubChildren中修改text的值:
// App
import React from 'react'
import store from './store/store'
import { ADDTEXT } from './store/actionType'
import Child from './components/Children'

class App extends React.Component {

  componentDidMount(){
    // 初始化store的值
    store.dispatch({
      type: ADDTEXT,
      value: 'value from App'
    })
  }

  render() {
    return (
      <Child  />
    )
  }
}

export default App

// Children(我只是工具人)
import React from 'react'
import SubChildren from './SubChildren'

class Child extends React.Component {

  render() {
    return (
      <>
        <SubChildren />
      </>
    )
  }
}

export default Child
// SubChildren
import React from 'react'
import store from '../store/store'
import { CHANGETEXT } from '../store/actionType'

class SubChildren extends React.Component<any, any> {

  constructor(props: any){
    super(props)
    this.state = {
      text: ''
    }
  }

  componentDidMount(){
    this.setState({
      text: store.getState().text
    })
    // 监听store的值并修改state.text
    store.subscribe(() => {
      this.setState({
        text: store.getState().text
      })
    })
  }

  handleChange(){
    // 修改store的值
    store.dispatch({
      type: CHANGETEXT,
      value: 'value from SubChildren'
    })
  }

  render() {
    return (
      <div>
       <p>{this.state.text}</p>
       <button onClick={this.handleChange}>修改</button>
      </div>
    )
  }
}

export default SubChildren

p.s 我们在react中使用redux的同时,有时候为了便利,还会搭配使用react-reduxredux-thunk:

  • react-redux:上面我们发现每个组件使用store都得引入一遍,但是我们可以使用react-redux从而直接在App中使用Provider注入就会方便很多,还可以使用connect将UI组件和容器组件结合起来,详情可以查看 React Redux;
  • redux-thunk: 使用dispatch的话,只能传入一个action对象,使用redux-thunk的话,可以传入一个函数。

总结

  • 了解React中的数据通信;
  • props是单向数据流,可以实现父子通信、子父通信和兄弟之间的通信,如果要实现其他通信反而会很鸡肋;
  • 可以使用发布订阅的模式进行多级、平级通信
  • Context API: 便于多级嵌套的组件进行通信;
  • redux:会在全局创建一个store,当用户在视图层面修改数据的时候,会dispatch一个actionstore收到之后传递给reducer去处理,reducer将处理好的值传递给storestore就会通知视图。

参考

深入浅出搞定 React


如有错误,欢迎指出,感谢~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值