react——组件通信

        组件通信分为父子组件之间的通信和非父子之间的通信 ,在父子组件通信中用到的是父传子(传递数据),字传父(传递方法),类组件多了个this,函数组件则不需要this。非父子组件通信,跨域的比较大,组件层级嵌套比较多,因此相对来说会比较复杂,但是也有方式来实现:状态提升(中间人模式,前提是必须是亲兄弟,关系复杂后不适用),发布订阅者模式(实际开发中用redux),context状态树传参(官方方法)

一、父子通信

(一)类组件

以下代码中级演示了父传子(通过属性名={传递的值} 来向子组件传值,子组件中通过this.props.属性名 来接受父组件传来的值)

也演示了子传父(子组件通过自身的方法,向父组件提交了一个功能,触发父组件的方法)

import React, { Component } from 'react'


// 父传子在上一个案例中讲过了,通过props属性传值

// 父组件
export default class App7 extends Component {
    state = {
        num: 1
    }
    // 父组件中的方法执行的事情
    addFnFat=()=>{
        this.setState({
            num:this.state.num+1
        })
    }
    render() {
        return (
            
{/* 通过 */}
子组件中触发的加一:{this.state.num}
{/* 通过属性名={传递的值} 来向子组件传值 通过方法名={方法} 来向子组件传递一个方法*/}
) } } // 子组件 class Sub1 extends Component { // 子组件通过自身的方法,像父组件提交了一个功能,触发父组件的方法 addfn = () => { // 触发父组件的传来的方法 this.props.addFnFat() } render() { return (
{/* 通过this.props.属性名 来接受父组件传来的值 */} 父组件传来的数值:{this.props.num}
) } }

react——组件通信_第1张图片

(二)函数组件

以下代码中级演示了父传子(通过属性名={传递的值} 来向子组件传值,子组件中通过props.属性名 来接受父组件传来的值)

也演示了子传父(子组件通过自身的方法,向父组件提交了一个功能,触发父组件的方法)

import React,{useState} from 'react'

export default function App9() {
    const [num,setNum] = useState(1)//定义一个变量num
    const addFnFat=()=>{
        setNum(num+1)//修改num,使之加一
    }
    return (
        
子传父更改的值:{num}
{/* 通过属性名={传递的值} 来向子组件传值 通过方法名={方法} 来向子组件传递一个方法*/}
) } function Sub(props) { const addfn = () => { // 触发父组件的传来的方法 props.addFnFat() } return (
{/* 通过props.属性名 来接受父组件传来的值 */} 父组件传来的数值:{props.num}
) }

react——组件通信_第2张图片

以上类组件和函数组件的父子通信,区别在于:类组件多了个this,函数组件则不需要this,后续的类组件和函数组件的区别是这个,不做过多的赘述。

二、非父子通信

(一)状态提升(中间人)

1、实现思想     

出现一个中间人,这个中间人的角色相当于一个父亲,管着两个儿子,这两个儿子之间的交流需要通过父亲来传递

react——组件通信_第3张图片

 2、代码实现:儿子2 想接受儿子1里面的数据,并实现加1

import React, { Component } from 'react'
/* 
儿子2 想接受儿子1里面的数据,并实现加1,
*/

// 中间人
export default class App11 extends Component {
    state={
        acceptNum:0//用来接受儿子1传来的值
    }

    tranNum = (sub1Value) => {
        console.log("能接受到儿子1传来的值吗?",sub1Value);
        // 将儿子1传来的值放到父亲身上
        this.setState({
            acceptNum:sub1Value
        })
    }
    tranNum2 = (sub2Value) => {
        console.log("能接受到儿子2传来的值吗?",sub2Value);
        // 将儿子1传来的值放到父亲身上
        this.setState({
            acceptNum:sub2Value
        })
    }
    render() {
        return (
            
{this.state.acceptNum}

{/* 将儿子1传来的值,通过父亲转接,传给儿子2 */}
{/* 儿子2更改后的的值传回给儿子1 */}
) } } // 儿子1 class Sub1 extends Component { state = { num: 1 } // 儿子1向父亲传递num里面的值 transferFn = (num) => { this.props.tranNum(num) } render() { return (
拿到儿子2通过加一后传来的值{this.props.sub2Num}
) } } // 儿子2 class Sub2 extends Component { // 儿子2修改了儿子1的值,并给父亲发了个指令 addFn=(sub1Num)=>{ var sub2Num = sub1Num+1 this.props.tranNum2(sub2Num) } render() { return (
儿子2经过父亲,拿到儿子1传来的值:{this.props.sub1Num}
) } }

react——组件通信_第4张图片

 弊端:只能在亲兄弟上实现,关系复杂之后不适用,因为传递的关系太复杂,不利于开发者开发,并且可以选择其他方式来实现这样的,比如跨组件的发布订阅者模式和context等

(二)发布订阅者模式

        实际开发不会使用原生的发布订阅者模式,会用redux(基于发布订阅者封装好的)方法来实现这种非父子通信的模式

1、实现思路

        发布者subsrcibe()存入函数到list数组等待运行,可多次发布

        订阅者调用publish()函数运行list数组中全部函数。订阅者不能写在发布者的前面,可多次订阅,订阅者可传参给发布者拿到

2、实现代码

import React, { Component } from 'react'

export default class App8 extends Component {
    render() {
        return (
            
发布订阅
) } } var bus = { list:[], // 发布 subscribe(callback) { console.log(callback); this.list.push(callback) }, // 订阅 publish(text) {//text传的参数 // 遍历所有的lisy,将回调函数执行 this.list.forEach(callback=>{ callback&&callback(text) }) } } // 订阅者 bus.subscribe((value)=>{//value接受发布者传来的参数 console.log('111',value); }) bus.subscribe(()=>{ console.log("121"); }) bus.publish("传进去的参数")

react——组件通信_第5张图片

(三)context状态树传参(官方方法)

        在新的context API中 React提供了一个createContext的方法,该方法返回了一个包含Provider,Consumer对象,需要注意的是:提供者一定要是消费者的某一层父级

1、实现过程

(1)先定义全局context对象

(2)根组件引入GlobalContext,并使用GlobalContext.Provider(生产者)

(3)类组件中,任意组件引入GlobalContext并调用Context,使用GlobalContext.Consumer(消费者),返回一个函数,并传入value。

value.变量  获取数据

value.方法  获取方法

{
    // 必须要是一个箭头函数
    (value) => {
        return (//必须要return出去
          
生产者提供的值:{value.num}
) } }

(4)函数式组件中,通过使用hooks提供的useContext来获取参数。

value.变量   获取数据

value.方法  获取方法

    const value = useContext(GlobalContext)
    console.log(value);
    const addFn = (value) => {
        value.changeNum()
    }
    return (
        
生产者提供的值:{value.num}
)

2、实现代码

(1)类组件:

import React, { Component, createContext } from 'react'
/* 
1、实现过程
(1)先定义全局context对象

(2)根组件引入GlobalContext,并使用GlobalContext.Provider(生产者)

(3)任意组件引入GlobalContext并调用Context,使用GlobalContext.Consumer(消费者)
*/
// 1、全局定义context对象
const GlobalContext = React.createContext()

// 生产者
export default class Context extends Component {
    state = {
        num: 1
    }
    render() {
        return (
            // 一定要为父标签,作为唯一的根标签
             {
                    this.setState({
                        num: this.state.num + 1
                    })
                }
            }}>

                

            
        )
    }
}

// 消费者
class Consumer extends Component {
    // 消费者向生产者发送的指令
    addFn = (value) => {
        // 执行生产者中的方法
        value.changeNum()
    }
    render() {
        return (
            
                {
                    // 必须要是一个箭头函数
                    (value) => {
                        return (//必须要return出去
                            
生产者提供的值:{value.num}
) } }
) } }

react——组件通信_第6张图片

(2)函数式组件-useContext()

没有引入useContext的写法

import React,{useState} from 'react'
// 1、全局定义context对象
const GlobalContext = React.createContext()
export default function App() {
    const [num,setNum] = useState(0)
    return (
        // 一定要为父标签,作为唯一的根标签
         {
                setNum(num + 1)
            }
        }}>

            

        
    )
}
function Sub1(){
    const addFn=(value)=>{
        value.changeNum()
    }
    return (
        
            {
                // 必须要是一个箭头函数
                (value) => {
                    console.log(value);
                    return (//必须要return出去
                        
生产者提供的值:{value.num}
) } }
) }

引入useContext的写法

import React, { useState, useContext } from 'react'
// 1、全局定义context对象
const GlobalContext = React.createContext()
export default function App() {
    const [num, setNum] = useState(0)
    return (
        // 一定要为父标签,作为唯一的根标签
         {
                setNum(num + 1)
            }
        }}>

            

        
    )
}
function Sub1() {
    const value = useContext(GlobalContext)
    console.log(value);
    const addFn = (value) => {
        value.changeNum()
    }
    return (
        
生产者提供的值:{value.num}
) }

react——组件通信_第7张图片

3、注意 

(1)提供者一定要是消费者的某一层父级

(2)消费者的结构必须必须要是一个箭头函数,而且必须要return出去

(3)函数组件的实现方式也是如此,只是存储的变量需要用useState

(四)Flux

        flux是一种架构思想,用来构建客户端应用的应用架构,专门解决软件的结构问题,为和react搭配使用,它跟mvc架构是同一类东西,利用单向数据流的方式结合react中的视图软件,但是更加简单清晰,容易上手,实际开发中用和这个比较少,用redux比较多

react——组件通信_第8张图片

 1、用户访问view

 2、view发出用户的action

 3、dispatcher收到action,要求store进行相应的更新

 4、store更新后,发出一个change事件

 5、view收到change事件后,更新页面

(五)Redux

1、概念

        官网中给出:Redux 是一个使用叫做 “action” 的事件来管理和更新应用状态的模式和工具库 它以集中式 Store(centralized store)的方式对整个应用中使用的状态进行集中管理,其规则确保状态只能以可预测的方式更新。Redux 基础教程,第一节:Redux 概述和概念 | Redux 中文官网

        官网的解释很官方,但是对学习者理解来说很吃力,而我们只要记住官方的概念,然后知道应用场景+如何使用就可以了

2、应用场景

        redux是一个有用的框架,但是不是非用不可,曾经有人说过:“如果你不知道是否需要redux那就是不需要它”。Redux 的创造者 Dan Abramov 也说过:“只有遇到react实在解决不了的问题,你才需要redux”,那么啥时候用呢?

从项目角度

        用户的使用方式复杂

        不同身份的用户有不同的使用方式(普通用户和管理员)

        多个用户之间可以协作

        与服务器大量交互,或者使用WebSocket

        View需要从多个来源获取数据

从组件角度

        某个组件的状态需要共享

        某个状态需要在任何地方都可以拿到

        一个组件需要改变全局状态

        一个组件需要改变另一个组件的状态

3、设计思想

        应用中使用集中式的全局状态来管理,并明确更新状态的模式,以便让代码具有可预测性。所有的状态都保存在一个对象里面,最终思想还是发布订阅者模式,有三个原则:

        唯一数据源

        保持只读状态

        数据改变只能通过纯函数来执行

react——组件通信_第9张图片

4、Redux 术语

(1)Action

描述应用程序中发生了什么事件,有两个参数

        type:String,给action起一个描述性的名字,通常的格式“域/事件名称”,域:是action所属的特征或类别;事件名称:具体发生的事情

        action:可以有其他字段,其中包含有关发生的事情的附加信息,平常将该信息放在名为payload的字段中

// 创建action对象 
const action = { 
    type: "changeInputValue", // type属性是必须要写的,用于校验 
    value: e.target.value, // value代表要修改为什么值
 }

(2)reducer

        是一个函数,接收当前的state(初始值)和一个action对象,必要是决定如何更新状态,并返回新状态。可以视为一个事件监听器,根据接受到的action类型处理事件

                state:指的是原始仓库里的状态

                action:指的是action新传递的状态

        函数内部的逻辑通常遵循的步骤:检查reducer是否关心action,如果关心则赋值state,使用新值更新state副本,然后返回新state;否则返回原来的state不变

//初始默认值 
const initialState = { 
    value: 0 
} 
//state:指的是原始仓库里的状态 
//action:指的是action新传递的状态 
function counterReducer(state = initialState, action) { 
    // 检查 reducer 是否关心这个 
    action if (action.type === 'counter/increment') { 
        // 如果是,复制 `state` 
        return { 
            ...state, // 使用新值更新 state 副本 
            value: state.value + 1 
        } 
    } 
    // 返回原来的 state 不变 
    return state 
}

通常用switch来遍历

(3)store

        相当于一个仓库,当前redux应用的state存在与一个名为store的对象中

        通过传入reducer来创建

        通过getState()来获取仓库的内容,返回当前状态值

// 引入createStore对象 
import { createStore } from 'redux' // 引入reducer 
import reducer from './reducer' 

const store = createStore( reducer ); 
export default store; //获取当前状态值 
// 引入store 
import store from './store' var xxx = store.getState()

(4)dispatch

        用来更新state的唯一方法是调用store.dispatch()并传入一个action对象,store 将执行所有 reducer 函数并计算出更新后的 state,调用 getState() 可以获取新 state。

store.dispatch({ type: 'counter/increment' }) 
console.log(store.getState()) // {value: 1}

5、安装Redux DevTools扩展

目的:在浏览器中调试redux状态值。在官网中给出了扩展对应浏览器的安装地址,这里适用我的是Chrome版本的,当然也可以自己搜索下载,不用解压,直接拽入到浏览器的扩展,再程序中配置window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),之后重启浏览器就可以使用了

// 引入createStore对象 
import { createStore } from 'redux' 
// 引入reducer 
import reducer from './reducer' 

const store = createStore( 
    reducer, 
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
); 
export default store;

6、使用案例——计数器

(1)创建项目安装

// 创建项目

    npx create-react-app 项目名

// 安装

    redux yarn add redux

(2)配置redux结构

在src下面新建redux之后新建index和reducer

index.js

// 引入createStore对象 
import { createStore } from 'redux' 
// 引入reducer 
import reducer from './reducer' 

const store = createStore( 
    reducer, 
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
); 
export default store;

reducer.js

//初始值 
const initState={ 
    num:0//用于计数的变量 
} 
// 导出一个函数,用于返回state 
export default (state = initState, action) => { 
    return state; 
}

(3)页面结构

(3)-1、函数组件形式

import React, { useState, useEffect } from 'react' 
import store from '../redux' 

export default function Count() { 
    const [state, setState] = useState(0) 
    // var state = store.getState() 
    // console.log("1",state.num); 
    console.log("store", store); 
    useEffect(() => { store.subscribe(() => { 
        setState(store.getState().num) }) 
    }, [state]) console.log("1", state); 
    return ( 
        
计数器
{state}
) } // 子组件1做加法 function AddFn() { const add = () => { console.log("jia"); const addAction = { type: "add" } store.dispatch(addAction) } return (
加1
) } // 子组件2做减法 function JianFn() { const jian = () => { console.log("jian"); const jianAction = { type: "jian" } store.dispatch(jianAction) } return (
减1
) }

(3)-2、类组件形式

import React, { Component } from 'react' import store from '../redux';

export default class Count1 extends Component {

        state = { num:0 }

        constructor(p){

                super(p)

                this.state={ num:0 }

                this.state = store.getState()

                store.subscribe(this.storeChange.bind(this))

                // console.log("11",this.state);

        }

        storeChange(){

                this.setState( store.getState() )

        }

        render() {

                return (

                
                                                 {this.state.num}                                          
        )      } } // 子组件1加 class Add extends Component {         add=()=>{                 console.log("jia ");                 store.dispatch({type:"add"})         }         render() {                 return (                         
加1
                )         } } // 子组件2减 class Jian extends Component {         jian=()=>{                 console.log("jian"); store.dispatch({type:"jian"})         }         render() {                 return (                         
减1
                )         } }

7、ActionTypes

        实际开发中,会写很多个action,其中的type就会出现很多个,为了方便管理和提高复用性,可以将action中的type抽离出来统一管理,在redux文件夹下新建actionTypes.js 文件

export const ADD = "add"; 
export const JIAN = "jian";
export const DEL_LIST_ITEM = "delListItem";

reducer.js 中导入actionTypes,并将type替换

import { ADD, JIAN, DEL_LIST_ITEM } from './actionTypes' 
const initState={ num:0 } 
// 导出一个函数,用于返回state 
export default (state = initState, action) => { 
    let newState = JSON.parse(JSON.stringify(state)); // 对原本的state做一次深拷贝                 
    console.log(action); 
    switch(action.type){ 
        case ADD: 
            newState.num +=1; 
            console.log(newState); 
            return newState; 
        case JIAN: 
            newState.num -=1; 
            return newState; 
        default: 
            break; 
    } 
    return newState;
}

页面中也导入actionTypes,并将type替换

//类组件 
import React, { Component } from 'react' 
import store from '../redux'; 
import { ADD, JIAN, DEL_LIST_ITEM } from '../redux/actionTypes' 

export default class Count1 extends Component { 
    state = { num:0 } 
    constructor(p){ 
        super(p) 
        this.state={ num:0 } 
        this.state = store.getState() 
        store.subscribe(this.storeChange.bind(this)) 
        // console.log("11",this.state); } 
        storeChange(){ 
            this.setState( store.getState() ) 
        } 
        render() { 
            return ( 
                
{this.state.num}
) } } // 子组件1加 class Add extends Component { add=()=>{ console.log("jia "); store.dispatch({type:ADD}) } render() { return (
加1
) } } // 子组件2减 class Jian extends Component { jian=()=>{ console.log("jian"); store.dispatch({type:JIAN}) } render() { return (
减1
) } }
//函数组件 
import React, { useState, useEffect } from 'react' 
import store from '../redux' 
import { ADD, JIAN, DEL_LIST_ITEM } from '../redux/actionTypes'
 
export default function Count() { 
    const [state, setState] = useState(0) 
    // var state = store.getState() 
    // console.log("1",state.num); 
    console.log("store", store); 
    useEffect(() => { 
        store.subscribe(() => { 
        setState(store.getState().num) }) 
    }, [state]) 
    console.log("1", state); 
    return ( 
        
计数器
{state}
) } // 子组件1做加法 function AddFn() { const add = () => { console.log("jia"); const addAction = { type: ADD } store.dispatch(addAction) } return (
加1
) } // 子组件2做减法 function JianFn() { const jian = () => { console.log("jian"); const jianAction = { type: JIAN } store.dispatch(jianAction) } return (
减1
) }

8、ActionCreator

实际开发中也会将action也统一管理在一个文件中actionCreator.js

import { ADD, JIAN, DEL_LIST_ITEM } from './actionTypes' 

// 计数器的加法 
export const addFnAction = () =>{ 
    return { type:ADD } 
} 

// 计数器的减法 
export const jianFnAction = () =>{ 
    return { type:JIAN } 
}

页面中删除之前的actionTypes导入,导入actionCreator,修改action

//类组件
import React, { Component } from 'react'
import store from '../redux';
import { addFnAction, jianFnAction} from '../redux/actionCreator'

export default class Count1 extends Component {
    state = {
        num:0
    }
constructor(p){
    super(p)
    this.state={
        num:0
    }
    this.state = store.getState()

    store.subscribe(this.storeChange.bind(this))
    // console.log("11",this.state);
}
storeChange(){
    this.setState(
        store.getState()
     )
}
    render() {
        return (
            
{this.state.num}
) } } // 子组件1加 class Add extends Component { add=()=>{ console.log("jia "); store.dispatch(addFnAction()) } render() { return (
加1
) } } // 子组件2减 class Jian extends Component { jian=()=>{ console.log("jian"); store.dispatch(jianFnAction()) } render() { return (
减1
) } }
//函数组件
import React, { useState, useEffect } from 'react'
import store from '../redux'
import { addFnAction, jianFnAction} from '../redux/actionCreator'

export default function Count() {
  const [state, setState] = useState(0)
  // var state = store.getState()
  // console.log("1",state.num);
  console.log("store", store);
  useEffect(() => {
    store.subscribe(() => {
      setState(store.getState().num)
    })

  }, [state])
  console.log("1", state);
  return (
    
计数器
{state}
) } // 子组件1做加法 function AddFn() { const add = () => { console.log("jia"); store.dispatch(addFnAction()) } return (
加1
) } // 子组件2做减法 function JianFn() { const jian = () => { console.log("jian"); store.dispatch(jianFnAction()) } return (
减1
) }

 最后必须要牢记:store必须是唯一的,不允许多个store,只能有一个store空间;只有store能改变自己的内容,reducer不能改变;reducer必须是纯函数

六、Flux和Redux的区别

        1、Flux只是一中思想,Redux是实现思想的方法

        2、Flux中有多个store来存储应用数据,并在store里面执行更新逻辑,当store变化的时候再通知controller-view更新自己的数据。Redux中只有一个store,可以根据这个store来得到完整的state,而且更新的逻辑也不再store中,而是在reducer(采用纯函数)中。
        3、Flux有Dispatcher这个概念。Redux没有Dispatcher这个概念,使用reducer来进行事件的处理,可以有多个reducer,每一reducer来负责维护应用整体state树中某一部分,多个reducer通过combineReducers方法合成一个根reducer,来维护整个statereducer是一个纯函数(preState, action) => newState,在Redux应用中。

你可能感兴趣的:(react学习,react.js,前端,前端框架)