组件通信分为父子组件之间的通信和非父子之间的通信 ,在父子组件通信中用到的是父传子(传递数据),字传父(传递方法),类组件多了个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}
)
}
}
以下代码中级演示了父传子(通过属性名={传递的值} 来向子组件传值,子组件中通过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}
)
}
以上类组件和函数组件的父子通信,区别在于:类组件多了个this,函数组件则不需要this,后续的类组件和函数组件的区别是这个,不做过多的赘述。
出现一个中间人,这个中间人的角色相当于一个父亲,管着两个儿子,这两个儿子之间的交流需要通过父亲来传递
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}
)
}
}
弊端:只能在亲兄弟上实现,关系复杂之后不适用,因为传递的关系太复杂,不利于开发者开发,并且可以选择其他方式来实现这样的,比如跨组件的发布订阅者模式和context等
实际开发不会使用原生的发布订阅者模式,会用redux(基于发布订阅者封装好的)方法来实现这种非父子通信的模式
发布者subsrcibe()存入函数到list数组等待运行,可多次发布
订阅者调用publish()函数运行list数组中全部函数。订阅者不能写在发布者的前面,可多次订阅,订阅者可传参给发布者拿到
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("传进去的参数")
在新的context API中 React提供了一个createContext的方法,该方法返回了一个包含Provider,Consumer对象,需要注意的是:提供者一定要是消费者的某一层父级
(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}
)
(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}
)
}
}
)
}
}
(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}
)
}
(1)提供者一定要是消费者的某一层父级
(2)消费者的结构必须必须要是一个箭头函数,而且必须要return出去
(3)函数组件的实现方式也是如此,只是存储的变量需要用useState
flux是一种架构思想,用来构建客户端应用的应用架构,专门解决软件的结构问题,为和react搭配使用,它跟mvc架构是同一类东西,利用单向数据流的方式结合react中的视图软件,但是更加简单清晰,容易上手,实际开发中用和这个比较少,用redux比较多
1、用户访问view
2、view发出用户的action
3、dispatcher收到action,要求store进行相应的更新
4、store更新后,发出一个change事件
5、view收到change事件后,更新页面
官网中给出:Redux 是一个使用叫做 “action” 的事件来管理和更新应用状态的模式和工具库 它以集中式 Store(centralized store)的方式对整个应用中使用的状态进行集中管理,其规则确保状态只能以可预测的方式更新。Redux 基础教程,第一节:Redux 概述和概念 | Redux 中文官网
官网的解释很官方,但是对学习者理解来说很吃力,而我们只要记住官方的概念,然后知道应用场景+如何使用就可以了
redux是一个有用的框架,但是不是非用不可,曾经有人说过:“如果你不知道是否需要redux那就是不需要它”。Redux 的创造者 Dan Abramov 也说过:“只有遇到react实在解决不了的问题,你才需要redux”,那么啥时候用呢?
从项目角度:
用户的使用方式复杂
不同身份的用户有不同的使用方式(普通用户和管理员)
多个用户之间可以协作
与服务器大量交互,或者使用WebSocket
View需要从多个来源获取数据
从组件角度:
某个组件的状态需要共享
某个状态需要在任何地方都可以拿到
一个组件需要改变全局状态
一个组件需要改变另一个组件的状态
应用中使用集中式的全局状态来管理,并明确更新状态的模式,以便让代码具有可预测性。所有的状态都保存在一个对象里面,最终思想还是发布订阅者模式,有三个原则:
唯一数据源
保持只读状态
数据改变只能通过纯函数来执行
(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}
目的:在浏览器中调试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;
(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
)
}
}
实际开发中,会写很多个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
)
}
实际开发中也会将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必须是纯函数
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应用中。