本文为入门级别的技术文章以及读书笔记,大佬请忽略。demo地址:连接javascript
父组件向子组件通信是咱们开发中很是常见,其方式也通常是props属性最直接方便,固然这里指的是直接就是父子组件关系的。vue
这里只但愿提示你们一点,若是你有须要,不要直接修改传入属性的值,若是你尝试直接修改会有提示不建议不该该这样操做,这个变量是只读的。若是你切实须要,你能够在state中接受并改变。java
通信的方式主要有两种,分别是自定义事件以及回调函数。其中回调函数很是方便,并且能够拿到执行时的全部状态;其中自定义事件比较麻烦,咱们通常使用比较少。node
在下面的例子中,我使用的方式是回调函数。react
// listItem.jsx
render() {
const { title } = this.state;
const { click } = this.props;
const title2 = title + "temp";
return (
<h2 onClick={click} onMouseDown={this.down}> {title2} </h2>
);
}
// list.jsx
render() {
const { list } = this.state;
const style = {
height: 30
};
return (
<div style={style}> {list.map(item => ( <ListItem {...item} click={this.click.bind(this)} error={this.error} /> ))} </div> ); } 复制代码
备注:若是你但愿传递事件之外侧参数,你须要bind绑定传参或者写成简单的箭头函数。不然,直接写函数以及参数会编译成函数直接执行。(好比传参text,onClick = {text => this.click(text), 或者写成,onClick={this.click.bind(this,text)}).git
兄弟组件若是是同一个父组件能够借助父组件进行通讯,这里很少描述,这里分享的是基于事件发布订阅机制的实现,咱们借助nodejs的Events模块。github
根目录下建立一个单例的事件,而后导出,为了不浪费,咱们全局使用一个单例文件events.js。npm
import {EventEmitter} from 'events';
export default new EventEmitter();
复制代码
// a组件中触发
import emitter from '../events';
emitter.emit('clickMsg','点击了')
// b组件监听
commpontentDidMount(){
this.clickMsg = emitter.on('clickMsg',data => {
console.log(data)
})
}
compontWillUnmount(){
emitter.removeListener(this.clickMsg)
}
复制代码
备注:事件的执行是否会屡次执行?目前遇到的一个问题,日志会执行屡次,文件中的说明,Hook a console constructor and forward messages to a callback 。json
跨级组件咱们也能够认为是父子组件的叠加,所以可使用多层父子组件的数据传递,但这样比较繁琐,所以更建议的方式是是经过context的方式,来实现。它的原理也比较简单,就是基于同一个父组件的前提下,它的任何层级的子组件均可以拿到这个父附件的状态,并以此进行通信。redux
在以前的官网版本中,一直具备这个功能,不过不建议使用而已,但随着版本迭代,慢慢发现这个功能具备较大的便利性。
// 定义一个context的全局对象:须要注意的是你在provider的地方使用它的时候也必须符合基本的数据结构
import React from "react";
export const ThemeContext React.createContext({
color: "black",
label: "名字"
});
// 父或者顶层容器设计
import { ThemeContext} from "../themeContext";
constructor(props){
super(props);
this.state = {
theme:{
color:'pink',
label:'你的名字'
}
}
}
render(){
return (
<ThemeContext.Provider value={this.state.theme}>
</ThemeContext.Provider>
)
}
// 任意层级的子组件
import {ThemeContext} from "../../themeContext";
render(){
<ThemeContext.Consumer>
{({ color, label }) => <label style={{ color: color }}>{label}</label>}
</ThemeContext.Consumer>
}
复制代码
在antd 的form组件中,咱们也看到对应的context的代码部分:文件地址:连接
// context 设置的默认值
import createReactContext, { Context } from 'create-react-context';
import { ColProps } from '../grid/col';
export interface FormContextProps {
vertical: boolean;
colon?: boolean;
labelAlign?: string;
labelCol?: ColProps;
wrapperCol?: ColProps;
}
export const FormContext: Context<FormContextProps> = createReactContext({
labelAlign: 'right',
vertical: false,
});
// 引入默认的context
import { FormContext } from './context';
// 获取传入的属性,而后解构,经过context provider的方式,state传值提供给包含在form组件中的组件
render() {
const { wrapperCol, labelAlign, labelCol, layout, colon } = this.props;
return (
<FormContext.Provider value={{ wrapperCol, labelAlign, labelCol, vertical: layout === 'vertical', colon }} > <ConfigConsumer>{this.renderForm}</ConfigConsumer> </FormContext.Provider> ); } 复制代码
虽然上面的方式提到了各类通信机制,但对于父组件须要主动触发子组件的某个事件仍是没有相应的方法。咱们在下面的例子中给出这种需求的场景。好比:在容器组件中,咱们须要主动刷新列表中的数据,而列表组件的数据是本身控制获取的。
备注:固然有的人会说,既然有如此的需求,为何不把子组件的数据或者方法经过父组件的属性传入,把逻辑在父组件中维护,这样就没有这样的问题。当然是一种很不错的方法,但也确实存在有些时候,咱们但愿子组件更多的具备一些功能,而不重度依赖父组件,只是将本身的事件暴露给父组件。其实这种设计思路在vue中,在开源的ui框架中不少,称为自定义事件,咱们除了属性传入控制组件以外,还能够经过组件的自定义事件,事件调用实现改变组件的机制。
class List extends React.Component{
constructor(props){
this.state ={
desc:''
}
}
xxxMethod(){
const desc = Math.random();
this.setState({
desc
})
}
render(){
const {list} = this.props;
return (
<div> <button onClick={this.xxxMethod.bind(this)}>点击变更标题描述</button> {list.map(text =>(<h2>{text}{desc}</h2>))} </div>
)
}
}
class parent extends React.Component{
refresh(){
// 能够调用子组件的任意方法
this.refs.List.xxxMethod();
}
//父组件
render (){
return (
<div> <button onClick={this.refresh.bind(this)}>点击变更标题描述</button> <List ref="List" /> </div>) } } 复制代码
经过回调事件传回this,而后赋值给父组件。看上去这段和ref的直接使用没有任何差异,不过能肯定的是经过这种方式,咱们能准确的拿到子组件的this实例。它为何是合理存在的呢?由于有些状况下,咱们经过ref拿到的不是咱们想要的子组件中的属性或者方法。
class Child extends React.Component{
constructor(props){
super(props);
}
componentDidMount(){
this.props.onRef(this)
}
console(){
console.log('子组件的方法');
}
render(){
return (<div>子组件</div>)
}
}
class Parent extends React.Component{
onRef(comp){
this.child = comp;
}
console(){
this.child.console()
}
render(){
return (
<div> <button onClick={this.console.bind(this)}>点击执行子组件方法</button> <Child onRef={this.onRef.bind(this)}/> </div> ) } } 复制代码
经过高阶组件的方式,咱们能够一次性解决相似的需求,而不是每次都手动的写this的暴露方法。在你须要的位置引入,而后父子组件分别写上@withRef。
import React from "react";
export default WrappedComponent => {
return class withRef extends React.Component {
static displayName = `withRef(${WrappedComponent.displayName || WrappedComponent.name || "Component"})`;
render() {
// 这里从新定义一个props的缘由是:
// 你直接去修改this.props.ref在react开发模式下会报错,不容许你去修改
const props = {
...this.props
};
// 在这里把getInstance赋值给ref,
// 传给`WrappedComponent`,这样就getInstance能获取到`WrappedComponent`实例
// 感谢评论区的[yangshenghaha]同窗的完善
props.ref = el => {
this.props.getInstance && this.props.getInstance(el);
this.props.ref && this.props.ref(el);
};
return <WrappedComponent {...props} />; } }; }; 复制代码
若是你的语法提示错误,不支持装饰器,你能够经过下面的设置:安装包依赖,npm install @babel/plugin-proposal-decorators。而后须要在package.json中设置插件的配置:
"babel": {
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
],
"presets": [
"react-app"
]
},
复制代码
经过本节,咱们熟悉了react基本的父子组件、兄弟组件、跨级组件之间如何进行通信,咱们须要知道的额外一点是,通信指的不只仅是数据的通信,也有事件的通信,好比主动的根据时机去唤起父组件或者子组件的事件,在组件中咱们能够很方便的经过属性调用父组件的方法或者传递的数据,咱们也须要知道如何将子组件的事件暴露给父组件使用。
其实重点在下一篇,组件抽象,固然就是说的高级组件,只不过此次从《深刻react技术栈》中获得了更多更全面的启示。点击跳转:react组件抽象