Skip to content

3 组件通信

约 637 个字 186 行代码 预计阅读时间 4 分钟

1 父子组件通信

  • Father-to-Son 通过 props 进行传递
  • Son-to-Father 通过 props 中的函数传递(函数在父组件中实现) => State 在何处,操作 State 的 method 就在何处

2 任意组件间通信

2.1 消息订阅与发布

此处使用 PubSub.js 实现,需要提前安装:npm i pubsub-js --save

import PubSub from 'pubsub-js' // 引入 @ both
// 订阅消息
componentDidMount() {
    PubSub.subscribe('msgName',(_,data) => { // _ 是占位符号
        this.setState({...data}) // 一个解构赋值的骚操作
    })
}
// 发布消息 @ any Function
function (){
    const dataobj = {
        name: '',
        age: 12
    }
    PubSub.publish('msgName', dataObj)
}

2.2 Redux

Redux 用于管理多个组件的共享状态,中文文档 首先需要安装 npm i redux --save

开发者工具

  1. 安装 npm i redux-devtools-extension
  2. store 中进行配置 js // @ redux/store import {composeWithDevTools} from 'redux-devtools-extension' const store = createStore( allReducers, composeWithDevTools(applyMiddleware(thunk)) )

三个核心概念

  1. Action 动作对象,包含两个属性:
    • type:字符串,唯一标识动作
    • data:任意类型,可选
  2. Reducer(必须是纯函数
    • 用于 初始化 / 加工 状态
    • 「加工」更具 old-state & action 生成新的 state
    • 不能直接操作 preState => 只能返回一个基于 preState 计算的 newState 所以不能用 preState.unshift -> return [data,...preState]
  3. Store - C位人士

精简版实现

我们在 /src/redux 下存放相关文件

  • 创建 Store
    // createStore 好像被弃用了,再说吧
    import { createStore } from 'redux'
    import countReducer from './countReducer'
    export default createStore(countReducer)
    
  • 创建用于计算实例的 Reducer
    const initState = 0 // 当传入 undefined 时进行初始化
    export default function countReducer(preState=initState, action) {
        const {type, data} = action // 提取 action 的相关信息
        // 根据 type 对 state 进行迭代修改
        switch(type) {
            case 'increase': return preState + data
            case 'decrease': return preState - data
            default: return preState
        }
        // 是否操作state的前置判断应该在调用 dispatch 前实现
    }
    
  • 发布更改指令
    import store from './redux/store'
    function add() {
        const {value} = this.selected // 获取选中数字
        // 发布加法指令
        store.dispatch({
            type: 'increase',
            data: value*1
        })
        // 此外,可以通过 store.getState 获取当前数据
    }
    
  • 增加订阅 因为 store 数据的改变不回触发重新 render,所以:
    import store from './redux/store';
    store.subscribe(()=>{
        root.render(<App />);
    })
    // 其实也可以在操作完之后调用一下 setState({})
    

完整版实现

  • 添加常量模块:避免拼写错误
    // @ redux/const.js
    export const INCREMENT = 'increment'
    export const DECREMENT = 'decrement'
    
  • 添加 ActionCreator - 生成 action 对象
    // @ redux/countActionCreator.js
    import {INCREAMENT} from './const'
    // 分别暴露 - 因为会有好几个函数
    export const createIncAction = (data)=>({ // 返回 Object
        type: INCREMENT,
        data
    })
    // 同样 reducer 应该 case INCREMENT
    
  • 引入并使用
    import {createIncAction} from './redux/createActionCreator'
    // 发送 dispatch 时使用官方手段创建
    store.dispatch(createIncAction(value))
    

异步 Action(不必要)

  • 同步 Action dispatch 一个包含 type & data 的普通对象
  • 异步 Action dispatch 一个 function

需要安装中间件 npm i redux-thunk,在 store 中应用以下更改

// @ redux/store.js
import thunk from 'redux-thunk'
import {createStore,applyMiddleware} from 'redux'
export default createStore(countReducer, applyMiddleware(thunk))

// @ redux/countActionCreator.js 添加异步 action
export const createAsyncIncreaceAction = (data, time) => {
    return (dispatch) => { // 返回 function
        setTimeout(()=>{ // 开启延时
            dispatch(createIncreaceAction(data)) // 调用同步action
        }, time)
    }
}
// 其实可以在组件里实现这个逻辑

2.3 react-redux

我们使用「容器组件」包裹 UI 组件,容器组件: - 直接和 Redux 交接,使用其 API(getState / dispatch) - 向UI组件提供:reudx中保存的状态 & 操作状态的方法(通过 props)

首先需要安装 npm i react-redux,随后创建容器组件

// @ container.js,引入UI组件及连接件
import UI from './UIcomp'
import {connect} from 'react-reduc'
// 引入 action
import { createIncAction, createDecAction } from './actionCreator'

// 返回一个对象(会合并到 props 中)
function mapStateToProps(state) {
    return {count: state}
}

// 返回对象,是 methods 的键值对
function mapDispatchToProps(dispatch) {
    return {
        add: (val) => dispatch(createIncAction(val)),
        sub: (val) => dispatch(createDecAction(val))
    }
}

// 两个参数分别将 data & methods 传递给 UI.props
export default connect(mapStateToProps, mapDispatchToProps)(UI)

// 可以在 UI 中使用 this.props.count / this.props.add 访问数据和方法

// store 在应用UI组件的文件中引入,并通过 props 传递
// @ UIcomponent
import store from './redux/store'
import Container from './container'
export default UI extends Component {
    render() {
        return (
            <Container store={store}/>
        )
    }
}

精简版本

// 对 container 进行一个简写的大动作
export default connect(
    state => ({count:state}), // 使用圆括号包裹返回的对象,避免歧义
    {   // react-redux 会自动进行分发,参数会直接被 actionCreator 接收
        add: createIncAction,
        sub: createDecAction,
        addAsync: createAsyncIncAction
    }
)(UI)
  • react-redux 通过 connect 监听了数据变化,在 index.js 中也不用手动可进行消息订阅了

  • 因为所有人都要用到 store,在使用组件的时候一个个 store={store} 实在太阴间哩,所以对 App 进行一个大的改造

    // @ index.jsx
    import store from './store'
    import {Provider} from 'react-redux'
    // 这一操作是的App的所有后代组件都接收store
    ReactDOM.render(
        <Provider store={store}>
            <App/>
        </Provider>,
        document.getElementById('root')
    )
    

  • 整合 UI & Container(1v1会导致文件数量 double,所以我们尝试整到一起)

    // 本质是在一个文件里定义俩组件,然后把 Container 交出去
    // 1. 首先定义本体的 UI 组件(照常从 props 中取用)、
    class Count extends Component {}
    // 2. 随后定义并交出 Container
    export default connect(state,methods)(Count)
    
    // 使用时以 Count 名义引入并使用就行(实际上引入的是 Container)
    

数据共享

  • 所有的操作定义在 同一个 const 文件中
  • 不同种类的操作应该定义独立的 actionMaker & Reducer
  • 需要在 store.js 中汇总所有的 Reducer
    // @ store.js
    // 引入所有的 Reducer
    import{combineReducers} from 'redux'
    const CombinedReducer = combineReducers({
        // 各个reducer操作的结果将作为 state 的对应分量
        sum: countReducer, // import同名时可触发简写
        list: personReducer
    })
    // 暴露整合的 store
    export default createStore(
        CombinedReducer,
        applyMiddleware(thunk)
    )
    
    • 进一步优化:在 redux/reducers/index.js 中引入并汇总
      import subReducer from './somewhere'
      export default AllReducers = {...}
      // @ store.js
      import AllReducers from './redux/reducer/index'
      // 将 AllReducers 作为 createStore 的第一个参数
      
  • 在初始化 Container 的时候对 state 做对应修改
    // UI 组件中使用的 props 分量基于此处 state 的重命名结果
    export default connnect(
        state => ({
            // 映射其他 reducer 修改的时候使用分量(支持重命名)
            // 分量名与 CombineReducer 时注册的一致
            personList: state.list,
            sum: state.sum
        }),
        {   // 仅映射自身使用的方法
            addPerson:createAddPersonAction // 名字起得好也能简写
        }
    )(Person)
    

2.3 后代组件通信

  1. 创建 Context 容器 const xxContext = Reace.createContext()
  2. 在 render 中使用 <xxContext> 包裹子组件,通过 value 传递数据
     // 单个数据 value={username}
     // 多个数据 value={{name:username, age:userage}}
     <xxContext.Provider value={dataName}>
         <DescComponent/>
     </xxContext.Provider>
    
  3. 在后代组件中读取数据
    // 1. rcc Only
    static contextType = xxContext; // 声明接收哪个Context
    console.log(this.context); // 读取 value 中传递的数据
    
    // 2. both rcc & rfc
    <Consumer>
        {
            value => {
                return `年龄是${value.age+1}` // 实际显示数据(可加工)
                // 也可以返回一个 template
            }
        }
    <Consumer/>