浅谈 TypeScript - 基础的组件通信

1,041 阅读5分钟

React 的世界中由于其本身的设计思路的原因,因此不同组件关于通信的问题,是一门很值得研究的基础方法,这些基础方法涵盖了在组件的使用过程中的方方面面,当然你也可以选择一些数据流的管理库来处理这个问题。那么,在 TypeScript 的世界里,我们又该如何去实现这些基础的通信方式呢?

普通 Props 传递

做为整个 React 的设计精髓 Props 来驱动整个数据的流向也就适应了它的使命,因此通过 Props 的传递,我们既可以解决从父组件到子组件的通信,也可以解决从子组件到父组件的通信。

从父组件将 Props 传递给子组件:

import * as React from "react";

interface IProps {
  text: string;
}

class ChildComp extends React.Component<IProps> {
  constructor(props: IProps) {
    super(props);
  }

  public render() {
    const {
      text,
      handler,
    } = this.props;
    return (
      <React.Fragment>
        <Typography>
          Props Text Value: { text }
        </Typography>
      </React.Fragment>

    );
  }
}

export default ChildComp;
export interface ICommunicationProps {}
export interface ICommunicationState {
  value: number;
}

import * as React from "react";
import Tabs from "@material-ui/core/Tabs";
import Tab from "@material-ui/core/Tab";
import Typography from "@material-ui/core/Typography";
import {
  ICommunicationProps,
  ICommunicationState,
} from "./types";
import ChildComp from "./components/ChildComp";

const TabContainer: React.SFC = (props) => {
  return (
    <Typography component="div" style={{ padding: 8 * 3 }}>
      {props.children}
    </Typography>
  );
};

class Communication extends React.Component<ICommunicationProps, ICommunicationState> {
  public childCompProps: string;
  constructor(props: ICommunicationProps) {
    super(props);
    this.state = {
      value: 0,
    };
    this.childCompProps = "icepy";
  }

  public handleChange = (event: React.ChangeEvent<{}>, value: any) => {
    this.setState({ value });
  }

  public render() {
    const { value } = this.state;
    return (
      <div>
        <Tabs value={value} onChange={this.handleChange}>
          <Tab label="普通 Props 传递" />
        </Tabs>
        {value === 0 && (
          <TabContainer>
            <ChildComp text={this.childCompProps}/>
          </TabContainer>
        )}
      </div>
    );
  }
}

export default Communication;

通过传递函数,让子组件与父组件进行通信:

import * as React from "react";
import Typography from "@material-ui/core/Typography";
import Button from "@material-ui/core/Button";
import styles from "./style.css";

interface IProps {
  text: string;
  handler?: (result: string) => void;
}

class ChildComp extends React.Component<IProps> {
  constructor(props: IProps) {
    super(props);
  }

  public handlerClick = () => {
    const { handler } = this.props;
    if (handler) {
      handler("ChildComp");
    }
  }

  public render() {
    const {
      text,
      handler,
    } = this.props;
    return (
      <React.Fragment>
        <Typography>
          Props Text Value: { text }
        </Typography>
        { handler && (
          <Typography>
            Props handler function 到父组件:
            <span className={styles.cons}>
              <Button variant="outlined" onClick={this.handlerClick} color="primary">
                确定
              </Button>
            </span>
          </Typography>
        )}
      </React.Fragment>

    );
  }
}

export default ChildComp;
import * as React from "react";
import Tabs from "@material-ui/core/Tabs";
import Tab from "@material-ui/core/Tab";
import Typography from "@material-ui/core/Typography";
import {
  ICommunicationProps,
  ICommunicationState,
} from "./types";
import ChildComp from "./components/ChildComp";

const TabContainer: React.SFC = (props) => {
  return (
    <Typography component="div" style={{ padding: 8 * 3 }}>
      {props.children}
    </Typography>
  );
};

class Communication extends React.Component<ICommunicationProps, ICommunicationState> {
  public childCompProps: string;
  constructor(props: ICommunicationProps) {
    super(props);
    this.state = {
      value: 0,
    };
    this.childCompProps = "icepy";
  }

  public handleChange = (event: React.ChangeEvent<{}>, value: any) => {
    this.setState({ value });
  }

  public emptyHandler = (result: string) => {
    alert(`child component click + ${result}`);
  }

  public render() {
    const { value } = this.state;
    return (
      <div>
        <Tabs value={value} onChange={this.handleChange}>
          <Tab label="普通 Props 传递" />
        </Tabs>
        {value === 0 && (
          <TabContainer>
            <ChildComp text={this.childCompProps} handler={this.emptyHandler} />
          </TabContainer>
        )}
      </div>
    );
  }
}

export default Communication;

基础组件通信

除了传递 Props 之外,也会有一些其他场景的地方需要完成组件之间的通信,比如兄弟节点之间,理论上 Props 可以涵盖基本所有的通信需求,之所以分别这其他几种方式出来,主要是从程序的设计角度来看,如果我们有一个很大的节点树,全部依赖 Props 会有比较多额外的 麻烦,这个麻烦并不是说程序不能运行,而是增加了很多额外的处理事项。

如果我们想将 A节点的数据传输给 B节点(它是在同一级),那么我们可以借助父节点来完成这次通信:

import * as React from "react";
import Typography from "@material-ui/core/Typography";
import Button from "@material-ui/core/Button";
import styles from "./style.css";

interface IProps {
  handler: (result: string) => void;
}

class ChildCompsR extends React.Component<IProps> {

  constructor(props: IProps) {
    super(props);
  }

  public handlerClick = () => {
    const { handler } = this.props;
    handler("ChildCompsR");
  }

  public render() {
    return (
      <div className={styles.container}>
        <Typography>
          <span className={styles.cons}>
            <Button variant="outlined" onClick={this.handlerClick} color="primary">
              state 操作
            </Button>
          </span>
        </Typography>
      </div>
    );
  }
}

export default ChildCompsR;
import * as React from "react";
import Tabs from "@material-ui/core/Tabs";
import Tab from "@material-ui/core/Tab";
import Typography from "@material-ui/core/Typography";
import {
  ICommunicationProps,
  ICommunicationState,
} from "./types";
import ChildCompsQ from "./components/ChildCompsS";
import ChildCompsR from "./components/ChildCompsS/ChildCompsR";

const TabContainer: React.SFC = (props) => {
  return (
    <Typography component="div" style={{ padding: 8 * 3 }}>
      {props.children}
    </Typography>
  );
};

class Communication extends React.Component<ICommunicationProps, ICommunicationState> {
  public childCompProps: string;
  constructor(props: ICommunicationProps) {
    super(props);
    this.state = {
      value: 0,
      brothersText: "",
    };
    this.childCompProps = "icepy";
  }

  public handleChange = (event: React.ChangeEvent<{}>, value: any) => {
    this.setState({ value });
  }

  public handlerChildCompsR = (result: string) => {
    this.setState({
      brothersText: result,
    });
  }

  public render() {
    const { value, brothersText, contextsText } = this.state;
    return (
      <div>
        <Tabs value={value} onChange={this.handleChange}>
          <Tab label="组件通信" />
        </Tabs>
        {value === 1 && (
          <TabContainer>
            <ChildCompsR
              handler={this.handlerChildCompsR}
            />
            <ChildCompsQ text={brothersText} />
          </TabContainer>
        )}
      </div>
    );
  }
}

export default Communication;

我们也可以通过 观察者 模式来处理同样的问题,如果你很了解它的话,那么这是一个非常的简单的处理方式,注册事件,发布事件就好,然后通过 setState 来变更数据的变化。

import * as React from "react";
import Typography from "@material-ui/core/Typography";
import Button from "@material-ui/core/Button";
import Proxy from "wolfy87-eventemitter";
import styles from "./style.css";

interface IProps {
  proxy: Proxy;
}

class ChildCompsR extends React.Component<IProps> {

  constructor(props: IProps) {
    super(props);
  }

  public handlerProxyClick = () => {
    this.props.proxy.emit("ChildCompsP", "ChildCompsR");
  }

  public render() {
    return (
      <div className={styles.container}>
        <Typography>
          <span className={styles.cons}>
            <Button variant="outlined"onClick={this.handlerProxyClick} color="primary">
              观察者 操作
            </Button>
          </span>
        </Typography>
      </div>
    );
  }
}

export default ChildCompsR;
import * as React from "react";
import Typography from "@material-ui/core/Typography";
import Proxy from "wolfy87-eventemitter";

interface IState {
  proxyText: string;
}

interface IProps {
  proxy: Proxy;
}

class ChildCompsP extends React.Component<IProps, IState> {
  constructor(props: IProps) {
    super(props);
    this.state = {
      proxyText: "",
    };
  }

  public componentDidMount() {
    this.props.proxy.on("ChildCompsP", (result: string) => {
      this.setState({
        proxyText: result,
      });
    });
  }

  public render() {
    const { proxyText } = this.state;
    return (
      <div>
        <Typography>
          Child Node ChildCompsP Proxy: { proxyText }
        </Typography>
      </div>
    );
  }
}

export default ChildCompsP;
import * as React from "react";
import Proxy from "wolfy87-eventemitter";
import Tabs from "@material-ui/core/Tabs";
import Tab from "@material-ui/core/Tab";
import Typography from "@material-ui/core/Typography";
import {
  ICommunicationProps,
  ICommunicationState,
} from "./types";
import ChildCompsR from "./components/ChildCompsS/ChildCompsR";
import ChildCompsP from "./components/ChildCompsS/ChildCompsP";

const TabContainer: React.SFC = (props) => {
  return (
    <Typography component="div" style={{ padding: 8 * 3 }}>
      {props.children}
    </Typography>
  );
};

const proxy = new Proxy();

class Communication extends React.Component<ICommunicationProps, ICommunicationState> {
  public childCompProps: string;
  constructor(props: ICommunicationProps) {
    super(props);
    this.state = {
      value: 0,
      brothersText: "",
    };
    this.childCompProps = "icepy";
  }

  public handleChange = (event: React.ChangeEvent<{}>, value: any) => {
    this.setState({ value });
  }

  public render() {
    const { value, brothersText } = this.state;
    return (
      <div>
        <Tabs value={value} onChange={this.handleChange}>
          <Tab label="普通 Props 传递" />
        </Tabs>
        {value === 1 && (
          <TabContainer>
            <ChildCompsR
              proxy={proxy}
            />
            <ChildCompsP proxy={proxy} />
          </TabContainer>
        )}
      </div>
    );
  }
}

export default Communication;

依然我们也可以使用 Context 的机制完成这次通信,使用 createContext 创建一个 Context,然后在根组件中使用MyContext.Provider 将你被 MyContext.Consumer 包裹的组件包裹起来,通过 value 来共享这一块的数据。

import * as React from "react";

export const MyContext = React.createContext("value");
import * as React from "react";
import Typography from "@material-ui/core/Typography";
import { MyContext } from "../../context";

class ChildCompsC extends React.Component {
  public render() {
    return (
      <MyContext.Consumer>
        {(value) => {
          return (
            <Typography>
              Child Node ChildCompsC Context: {value}
            </Typography>
          );
        }}
      </MyContext.Consumer>
    );
  }
}

export default ChildCompsC;
<MyContext.Provider value={contextsText}>
  <ChildCompsC />
</MyContext.Provider>

详情可参考 github.com/welearnmore…

这些基础的通信方法在我们的开发过程中非常的有用,你应该牢记这些基础通信方法,为了更好的写 TypeScript 版的 React,你还应该对类型有一些对齐的约束,这个约束在越往后使用的过程中得到的收益才更大。