总述:
组件间通讯有什么方法:
1) 使用props传值
2) 使用context传值( Provider
和 Consumer
)
3) 使用redux传值
非跨组件传参,以上传参方式都可用,比较常用也好用的是父子props传参;
跨组件传参,使用props一层层往下传,使用context,或者用redux 。
和vue的传参类比学习,套路相似,略有差别,对比着学习更易懂,可以一起看Vue传值(父子传值,provide/inject传值,vuex传值)
一、父子组件传参(传值、传方法)
1. 父 --->子
父组件:
<Child
定义个属性1 = { this.state.值 };
定义个属性2 = { this.方法名 };
/ >方法名=( )=>{ }
子组件:
值:this.props.属性1;
方法:this.props.属性2( );
2. 子 --->父
一句话可以概括,父组件向子组件传递属性值,子组件去触发父组件方法。
子组件不能直接修改父组件的值,遵从单项数据流思想,只能子组件通知父组件自己去进行增删改查。
传值:
父组件:
<Child
定义个属性2 = { this.方法名 }
/ >方法名=()=> {
// 获取的子组件的参数存在自己组件的state里
}
子组件:
this.props.属性2(子组件的参数)
传方法:
说在实现方式前面的话:
- function
- 对于用function定义的组件是没有办法用ref获取的,原因是: ref回调函数会在组件被挂载之后将组件实例传递给函数。但是使用function定义的函数并没有实例。
- 但是仍然可以获取function定义的组件中的DOM元素
- class
- 用class定义的组件由于可以获取组件实例,因此ref函数会在组件挂载的时候将实例传递给组件
将ref回调函数作用于某一个React组件,此时回调函数会在当前组件被实例化并挂载到页面上才会被调用。
ref回调函数被调用时,会将当前组件的实例作为参数传递给函数。
下面是类组件的实现方式:
1)组件没有使用redux时, 需要给子组件属性添加onRef={(ref)=>{ this.child = ref }},添加ref属性可以获取子组件的所有信息。
子组件:
// 在组件完成挂载后,去把this传给父
componentDidMount(){
this.props.onRef(this);
}
myName=()=>{
console.log('哈哈哈,我可以被父组件调用了,耶!');
}
父组件:
<Child onRef={(ref)=>{ this.child = ref}} />
<button onClick={this.click} >click</button>
click = (e) => {
this.child.myName()
}
2)组件使用了redux时,redux属于高阶组件,他会包裹住我们的子组件,导致ref无法获取,那就需要在连接器的参数上设置一下,把withRef打开。而父组件在调用子方法的时候,也需要使用getWrappedInstance获取。
子组件:
export default connect(stateToProps, null, null, { withRef: true }) (Child);
父组件:
onClick = () => {
this.Child.getWrappedInstance.myName();
}
二、跨组件传参
1. 使用props一层层往下传(繁琐,不好用,有时也用)
2. 使用context
3. 使用redux
1. 使用 context 的3种情况:
React.createContext
提供的Provider
和Consumer (推荐使用)
- 函数组件:
React.createContext
提供的Provider
和useContext
钩子- Class组件:
React.createContext
提供的Provider
和class
的contextType
属性
以下是父组件使用Provider
生产数据,子孙组件使用Consumer
消费数据:
父组件 ( MyContext.Provider):
import React, { createContext } from "react";
//light是默认值,当Consumer向上都找不到对应的provide时显示
const MyContext = createContext({name:'light'});
export default function App() {
return (
//Provider组件接收一个value属性,此处传入一个带有name属性的对象
<MyContext.Provider value={{ name: `context's value is string!` }}>
{/*这里写后面要进行包裹的子组件,此处先行导入后续需要消费context的组件*/}
<Children/>
</MyContext.Provider>
);
}
子组件、子孙组件( MyContext.Consumer) :
import React, { useReducer } from "react";
const MyContext = createContext();
const Children= () => {
return (
<MyContext.Consumer>
{(value) => {
return (
<div>
使用Context方式获取的值:{JSON.stringify(value)}
</div>
);
}}
</MyContext.Consumer>
);
};
export default Children;
2. 使用 redux
1) 安装react-redux插件
npm install --save react-redux
2) 在根组件里注入store (index.jsx)
import ReactDOM from 'react-dom';
import React from 'react';
import App from './App';
import { Provider } from 'react-redux'
import store from './store';
ReactDOM.render(
// 提供store仓库给所有的组件,利用Provider将祖先组件包裹起来
// 通过Provider的store属性将Redux的store传递给Provider,那么就可以在所有后代中直接使用Redux了
<Provider store={store}>
<App/>
</Provider>
, document.getElementById('root'));
3)reducer给store设置数据 (store.js)
注:store仓库本身是没有数据的,所以需要有reducer给它设置数据,需要与store进行结合
import { createStore } from 'redux';
import reducer from '../reducers';
const store = createStore(calculate);
export default store;
4) 定义action(action.js)
const ADD_MODAL = 'ADD_MODAL';
const DEL_MODAL = 'DEL_MODAL';
const GETlISTS='GETlISTS';
const SHOWLOADING='SHOWLOADING';
export const add = item => dispatch => {
dispatch({
type: ADD_MODAL,
payload: item
})
}
export const del = item => dispatch => {
dispatch({
type: ADD_MODAL,
payload: item
})
}
export const getListData = (payload) => {
return {
type: 'GETlISTS',
payload,
};
};
export const showLoading = (payload) => {
return {
type: 'SHOWLOADING',
payload,
};
};
5)reducers实现state数据变化 (reducers.js)
注:具体的操作执行交由reducer完成(reducer是数据的处理中心,数据的初始化,修改都在reducer中完成)
/**
* 函数reducer,它负责根据需求去修改state里面的值,也可指定初始化值
* 接收两个参数state 和action ,state可以取出里面要修改的值
*/
import { ADD_MODAL, DEL_MODAL } from '../action'
const initState = 0
const calculate = (state = initState, action) => {
//根据type决定如何加工数据
switch (action.type) {
case ADD_MODAL:
return {...state,modal:true}
case DEL_MODAL :
return {...state,modal:false}
default:
return state
}
}
export default calculate
5) 或调接口,直接存在redux里 (middle.jsx)
import {getListData,showLoading} from './action';
export function fetchList() {
return (dispatch) => {
//数据没有请求前改变loading的state状态值
dispatch(showLoading(true));
let url = 'https://api/posts';
fetch(url)
.then((res) => {
return res.json();
})
.then((data) => {
dispatch(getListData(data));
dispatch(showLoading(false));
});
};
}
6) 在需要使用的组件中进行引入{connect}, 以及在页面中使用 (demo.jsx)
import React, { Component } from 'react';
import { connect } from "react-redux";
import { add,del,fetchList} from './actions/count';
class demo extends Component {
constructor(props) {
super(props)
this.state = {
modals:true;
}
}
const {modal,add,del,fetchList,loading}= this.props;
render() {
return (
<div>
<h1>页面</h1>
<div>{modals}</div>
<button onClick={() =>{add()}}>
增加
</button>
<button onClick={() =>{del()}}>
减少
</button>
{loading? <button onClick={fetchList}>获取数据</button>:null}
</div>
)
}
}
/*reducer的state是无法实现数据传递的,因为它是状态,但是,它又需要与组件进行沟通,所以需要将state转成props才能进行属性传递,通过mapStateToProps转化*/
const mapStateToProps = (state) => ({
modals:state.modal,
loading: state.loading,
})
/*事件就是动作,动作就是函数,可以定义一个mapDispatchToProps,因为事件是无法进行传递的,唯一能够传递的内容只有属性props, mapDispatchToProps只做派发操作,不做具体动作执行*/
const mapDispatchToProps = (dispatch) => {
return {
add(data){ dispatch(add(data)); },
del(data){ dispatch(del(data)); },
fetchList,
}
}
//props属性与组件之间还没有建立起联系,所以需要利用connect进行关联
export default connect(mapStateToProps,mapDispatchToProps )(demo)
参考资料:
https://www.jianshu.com/p/e74202ec3127
https://juejin.cn/post/6924506511511126029