组件通信
组件为什么需要通信呢?这是因为组件是独立且封闭的单元,默认情况下,组件只能使用自己的数据,但是多个组件之间不可避免的要共享某些数据,为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通。
父传子
React 中的数据只能从“辈分高”的组件传到“辈分低”的组件,所以 React 中数据只能是单向数据流,是自顶而下的。
- 父组件将数据传给子组件通过 Props 实现,使用子组件的时候通过属性绑定数据,在组件内部通过 props 获取即可。
- 父组件传递数据给子组件,父组件更新数据子组件自动接收更新后的数据,子组件是不能修改数据的。
props
可以传递的数据类型
- 字符串
- 数字
- 布尔
- 数组
- 对象
- 函数
- JSX (插槽)
类型检查
由于 props 都是别的组件传递的,在使用的时候如果数据类型不对,很容易造成组件内部逻辑出错,为了避免这种情况可以通过 prop-types 插件在创建组件的时候进行类型检查。
- 安装 yarn add prop-types
常见的校验规则
- 常见类型:array、bool、func、number、object、string
- React元素类型:element
- 必填项:isRequired
- 特定结构的对象:shape({})
函数组件
import propTypes from 'prop-types'
function Father() {
return (
<Son name='孤城浪人'></Son>
);
}
function Son(props) {
return <div>{props.name}</div>
}
Son.propTypes = {
name: propTypes.number
}
- 类组件
function Father() {
return (
<Son name="孤城浪人"></Son>
);
}
class Son extends React.Component {
static propTypes = {
name: propTypes.string
}
render() {
return <div>{this.props.name}</div>
}
}
默认值
给组件的 props 设置默认值,在未传入props的时候生效。有两种方式
- 函数组件
function Father() {
return (
<Son></Son>
);
}
function Son(props) {
return <div>{props.name}</div>
}
Son.defaultProps = {
// props属性:校验规则
name: '孤城浪人'
}
新版react推荐使用参数默认值来实现
// 分页组件
function Father() {
return (
<Son></Son>
);
}
function Son({ name = '孤城浪人' }) {
return <div>{name}</div>
}
- 类组件
function Father() {
return (
<Son></Son>
);
}
class Son extends React.Component {
static defaultProps = {
name: '孤城浪人'
}
render() {
return <div>{this.props.name}</div>
}
}
函数组件
函数组件接收一个参数就是 props,当然作为形参可以任意命名,但是一般参数名就是 props。
<body>
<div id="root"></div>
<script type="text/javascript" src="./js/react.development.js"></script>
<script type="text/javascript" src="./js/react-dom.development.js"></script>
<script type="text/javascript" src="./js/babel.min.js"></script>
<script type="text/babel">
// 渲染虚拟dom
function Father() {
return (
<div>
<Son myKey="姓名" value="孤城浪人" />
<Son myKey="年龄" value="22" />
</div>
)
}
function Son(props) {
return <div>{props.myKey}:{props.value}</div>
}
ReactDOM.render(<Father />, document.getElementById('root'));
</script>
</body>
类组件
类组件会自动将 props 绑定在当前组件实例上。
<body>
<div id="root"></div>
<script type="text/javascript" src="./js/react.development.js"></script>
<script type="text/javascript" src="./js/react-dom.development.js"></script>
<script type="text/javascript" src="./js/babel.min.js"></script>
<script type="text/babel">
// 渲染虚拟dom
class Father extends React.Component {
state = {
info: [{
myKey: '姓名',
value: '孤城浪人'
}, {
myKey: '年龄',
value: '22'
}]
}
render() {
return (
<div>
{this.state.info.map(item => <Son key={item.value} myKey={item.myKey} value={item.value} />)}
</div>
)
}
}
class Son extends React.Component {
render() {
return <div>{this.props.myKey}:{this.props.value}</div>
}
}
ReactDOM.render(<Father />, document.getElementById('root'));
</script>
</body>
子传父
上面说过子组件不能直接修改父组件传来的 props,并且父组件中数据改变后子组件自动接收更新后的数据,利用这两个特性 React 提供了一种机制间接的修改。父组件传给子组件一个函数,该函数能够修改父组件的数据,当子组件需要修改数据时,就调用该函数,让父组件帮自己改,最后自动接收修改后的数据。
步骤:
- 父组件提供回调函数,通过 props 传递给子组件
- 子组件调用 props 中的回调函数,函数可传参
- 父组件函数的参数就是子组件传递的数据
<body>
<div id="root"></div>
<script type="text/javascript" src="./js/react.development.js"></script>
<script type="text/javascript" src="./js/react-dom.development.js"></script>
<script type="text/javascript" src="./js/babel.min.js"></script>
<script type="text/babel">
// 渲染虚拟dom
class Father extends React.Component {
state = {
info: [{
myKey: '姓名',
value: '孤城浪人'
}, {
myKey: '年龄',
value: 22
}]
}
addAge = () => {
this.setState(preState => {
console.log(preState)
preState.info.forEach(element => {
if (element.myKey == '年龄') {
element.value++;
}
});
return preState;
});
}
render() {
return (
<div>
<Son myKey={this.state.info[0].myKey} value={this.state.info[0].value} />
<Son myKey={this.state.info[1].myKey} value={this.state.info[1].value} addAge={this.addAge} />
</div>
)
}
}
class Son extends React.Component {
render() {
return <div onClick={this.props.addAge}>{this.props.myKey}:{this.props.value}</div>
}
}
ReactDOM.render(<Father />, document.getElementById('root'));
</script>
</body>
兄弟组件通信
开发中需要通信的组件之间当然不只是父子关系,还有兄弟关系,如下图子组件1和子组件2。
我们可以子组件 1 和子组件 2 都用到的数据保存在父组件中,在通过 props 传给子组件,这样无论是子组件 1 还是子组件 2 通过父子通信的方式修改了数据,子组件都能接收到修改后的数据。
<body>
<div id="root"></div>
<script type="text/javascript" src="./js/react.development.js"></script>
<script type="text/javascript" src="./js/react-dom.development.js"></script>
<script type="text/javascript" src="./js/babel.min.js"></script>
<script type="text/babel">
class Father extends React.Component {
state = {
myKey: '年龄',
value: 22
}
addAge = () => {
this.setState(preState => preState.value++);
}
render() {
return (
<div>
<Son1 myKey={this.state.myKey} value={this.state.value} addAge={this.addAge} />
<Son2 myKey={this.state.myKey} value={this.state.value} addAge={this.addAge} />
</div>
)
}
}
class Son1 extends React.Component {
render() {
return <div onClick={this.props.addAge}>{this.props.myKey}:{this.props.value}</div>
}
}
class Son2 extends React.Component {
render() {
return <div onClick={this.props.addAge}>{this.props.myKey}:{this.props.value}</div>
}
}
ReactDOM.render(<Father />, document.getElementById('root'));
</script>
</body>
祖孙组件通信
这种嵌套极深的组件间通信目前来说有两种方式
- 层层传递 props
- context 技术
层层传递实现
这种实现方式比较麻烦,而且也不利于后期维护,若是需要修改一个属性名,那么层层组件都需要修改。
<body>
<div id="root"></div>
<script type="text/javascript" src="./js/react.development.js"></script>
<script type="text/javascript" src="./js/react-dom.development.js"></script>
<script type="text/javascript" src="./js/babel.min.js"></script>
<script type="text/babel">
// 渲染虚拟dom
class Father extends React.Component {
state = {
myKey: '年龄',
value: 22
}
addAge = () => {
this.setState(preState => preState.value++);
}
render() {
return (
<div>
<Son myKey={this.state.myKey} value={this.state.value} addAge={this.addAge} />
</div>
)
}
}
class Son extends React.Component {
render() {
return <Grandson addAge={this.props.addAge} myKey={this.props.myKey} value={this.props.value} />
}
}
class Grandson extends React.Component {
render() {
return <div onClick={this.props.addAge}>{this.props.myKey}:{this.props.value}</div>
}
}
ReactDOM.render(<Father />, document.getElementById('root'));
</script>
</body>
context 实现
上下文,一个范围,只要在这个范围内,就可以跨级组件通讯。(不需要 props 层层传递)。React 会往上找到最近的 theme Provider,然后使用它的值。
我的理解就是类似发布订阅模式,祖辈组件发布数据,孙辈组件订阅数据(个人理解)。
<body>
<div id="root"></div>
<script type="text/javascript" src="./js/react.development.js"></script>
<script type="text/javascript" src="./js/react-dom.development.js"></script>
<script type="text/javascript" src="./js/babel.min.js"></script>
<script type="text/babel">
const infoContext = React.createContext({});
class Father extends React.Component {
state = {
myKey: '年龄',
value: 22,
}
addAge = () => {
this.setState(preState => preState.value++);
}
render() {
return (
<infoContext.Provider value={this.state}>
<div>
<Son />
</div>
</infoContext.Provider>
)
}
}
class Son extends React.Component {
render() {
return <Grandson />
}
}
class Grandson extends React.Component {
// 指定 contextType 读取当前的 theme context。这是不能少的
static contextType = infoContext;
render() {
console.log(this.context)
return <div onClick={this.context.addAge()}>{this.context.myKey}:{this.context.value}</div>
}
}
ReactDOM.render(<Father />, document.getElementById('root'));
</script>
</body>
第三方库
除了上述的几种组件间通信方式还可以使用第三方插件例如 redux,我的理解就是他们提供一个仓库,存放着所有的需要共享的数据,任何组件都可以从这个仓库拿数据、修改数据。如下图
但是这里就不做过多记录,后边学习到这些知识在记录一下。我是孤城浪人,一名正在前端路上摸爬滚打的菜鸟,欢迎你的关注。