0%

在业务中使用React,不可避免地会遇到需要各种组件之间进行通信的情况,这些情况总结起来大概分为以下几种:

  • 父组件向子组件通信
  • 子组件向父组件通信
  • 跨级组件通信
  • 非嵌套组件之间通信

父组件向子组件通信

这是最简单也是最常用的一种组件通信方式,父组件通过props将信息传递给子组件,子组件在接收到props后进行相应的操作。

父组件Parent.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, {useEffect, useState} from 'react'; 
import Child from './Child';
const Parent = () => {
const [message, setMessage] = useState<string>('');
useEffect(() => {
setMessage('我是消息,我会传递给子组件');
},[]);
return (
<div>
<span>我是父组件</span>
<Child message={message}/>
</div>
);
};
export default Parent;

子组件Child.tsx

1
2
3
4
5
6
7
8
9
10
import React from 'react';
const Child = (props: any) => {
const { message } = props;
return (
<div>
<span>{message}</span>
</div>
);
};
export default Child;

在上面例子中,父组件定义了message,并将其作为props传递给子组件,子组件接收props,并将其显示。

子组件向父组件通信

在某些情况中,当子组件的某些值改变,也需要通知父组件做出某些操作或改变。React中遵循单向数据流的原则,不可以直接通过改变props的值来达到改变父组件值的目的,正确的使用方式是:父组件将一个函数作为props传递给子组件,子组件调用该函数来与父组件通信。

父组件Parent.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React, {useEffect, useState} from 'react';
import Child from './Child'
const Parent = () => {
const [message, setMessage] = useState<string>('');
useEffect(() => {
setMessage('我是父组件的消息');
},[]);
const changeMessage = () => {
setMessage('我是父组件的消息,我改变啦!');
}
return (
<div>
<span>{message}</span>
<Child changeMessage={changeMessage}/>
</div>
);
};
export default Parent;

子组件Child.tsx

1
2
3
4
5
6
7
8
9
10
11
import React from 'react';
const Child = (props: any) => {
const { changeMessage } = props;
return (
<div>
<button onClick={changeMessage}>点我父组件的message会改变噢</button>
</div>
);
};
export default Child;

在上面例子中,父组件定义了changeMessag方法,并将其作为props传递给子组件,子组件接收props,当子组件点击按钮的时候执行props中的changeMessag方法,改变父组件中message的值。

跨级组件通信

所谓跨级组件通信,就是指父组件向子组件的子组件通信,向更深层的子组件通信。跨级组件通信可以采用下面两种方式:

  • 通过props一层一层向下传递
  • 使用context对象

对于第一种方式,如果组件嵌套的层次比较深,中间的每一层组件都要传递props;缺点是显而易见的,增加了程序的复杂度,而且对于中间层组件来说,这些props并不是自己所需要的,增加冗余代码,使代码并不十分友好,所以如果嵌套的层次比较深的话,斟酌使用这种方式。

使用context对象时另一种解决跨组件通信的方式,context相当于一个全局变量,是一个大容器,我们可以把要通信的内容放在这个容器中,这样一来,不管嵌套有多深,都可以随意取用。相对于第一种通信方式,这种方式显得更加友好。

myContext.ts——上下文管理的组件,用来统一导出 Context 实例

1
2
import React from 'react'
export const MyContext = React.createContext({message: '', changeMessage: (value: string) => {}});

父组件Parent.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, {useState, createContext} from 'react';
import Child from './Child'
import { MyContext } from '../context/myContext'
const Parent = () => {
const [message, setMessage] = useState<string>('传给孙子组件');
const changeMessage = (value: string) => {
setMessage(value);
}
return (
<div>
<MyContext.Provider value={{message, changeMessage}}>
<span>我是父组件</span>
<Child />
</MyContext.Provider>
</div>
);
};
export default Parent;

子组件Child.tsx

1
2
3
4
5
6
7
8
9
10
11
import React from 'react';
import GrandChild from './GrandChild'
const Child = () => {
return (
<div>
<span>我是子组件</span>
<GrandChild />
</div>
);
};
export default Child;

孙子组件GrandChild.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, {useContext} from 'react'
import { MyContext } from '../context/myContext'
const GrandChild = () => {
const {message, changeMessage} = useContext(MyContext);

return (
<div>
我是孙子组件
<button onClick={() => (changeMessage('改变了'))}>
{message}
</button>
</div>
);
};
export default GrandChild;

以上例子中,父组件使用上下文MyContext,并给其赋值message为当前组件的state变量message,changeMessage为当前组件定义的变量changeMessage,用于改变message的值。在孙子组件中,也引入MyContext上下文,并将其作为useContext的参数,从而得到父组件中的message和changeMessage。在孙子组件中点击按钮,直接调用MyContext透传过来的方法,可以修改父组件的message,孙子组件则会重新渲染。这种方式显式的避免了多级 props 的层层透传问题,对于嵌套层次比较深的组件是比较方便的。

非嵌套组件之间通信

非嵌套组件,就是没有任何包含关系的组件,包括兄弟组件以及不在同一个父级中的非兄弟组件。对于非嵌套组件,可以利用二者共同父组件的 context 对象进行通信。

在实际经验中,经常会遇到这样一些情况,给某个元素设置了margin-top,margin-bottom实际运行却看不到效果,往往是因为发生了边距重叠。

什么是外边距重叠?

边距重叠(也叫边距合并)是指:两个或多个盒子(可能相邻或嵌套)的垂直方向的相邻外边界重合在一起,形成一个外边界的情况。

Read more »

css盒模型是html+css中最核心的基础知识,那么什么是盒模型?
css盒子模型又称框模型 (Box Model) ,包含了元素内容(content)、内边距(padding)、边框(border)、外边距(margin)几个要素。如图:

标准盒模型和IE盒模型

盒模型有标准盒模型和IE盒模型两种模式,两者的区别在于对元素的width和height的计算方式不同:

在标准盒模型中:盒子的大小就是element的width和height;而在IE盒模型中:盒子的宽=element的width+(padding-left)+(padding-right)+(border-left)+(border-right);盒子的高=element的height+(padding-top)+(padding-bottom)+(border-top)+(border-bottom)

设置box-sizing属性为 content-box可以将一个盒模型设置成标准盒模型,设置box-sizing属性为border-box可以将一个盒模型设置成IE盒模型;浏览器中默认使用的事标准盒模型。

如何通过JS获得盒模型的宽度和高度

dom.style.width/height

可以通过style属性获取盒子宽度高度,上面写的dom代表页面上的元素(如:通过document.getElementById获取到的元素,下面的dom代表同样的意思),利用元素的style属性是可读可写的,也就是说可以通过style属性获取到某个元素的宽高,通过设置style属性宽高的值也可以设置元素的宽高。我们都知道在给元素写样式的时候有几种方式,可以直接写在标签上面,在标签内设置style属性,通常称为内联样式;也可以在页面上使用<style>标签来为页面上的元素添加样式,这种也被人称为内嵌样式;还有一种就是写一个专门的css文件,用来放页面样式代码,这种常被称为外联样式。但是值得一提的是,通过style属性只能获取到内联样式上面设置的属性值,通过style属性获取到的值是没有经过浏览器渲染的值。

运行结果如下:

dom.currentStyle.width/height

currentStyle属性完美解决了上面style的两个问题,它可以获取到通过任何方式设置的元素的样式,获取到的样式的值是经过浏览器渲染后的,最终显示的值;但是,它也存在一个问题,这个属性只在IE上能正确运行。

window.getComputedStyle()

window.getComputedStyle(“元素”,”伪类”)可以获取到指定元素的所有属性和样式,其返回值是一个对象。想要获取不同的属性值,可直接在window.getComputedStyle()的返回对象上取得相应值。这种方式获得的值也是浏览器渲染后的值,前面说过,这个方法的返回值是一个对象,其中包括元素的所有样式,如果某个样式没有在样式中显示设置,也可以获得浏览器为该元素设置的默认的值,也即是浏览器渲染后显示的样式。
这种方式的缺点是不支持IE8及以下版本的IE,在其他的浏览器基本上都支持采用这种方式获取页面渲染后的样式。


运行结果如下

dom.getBoundingClientRect().width/height

getBoundingClientRect()方法可以获得元素相对于视口的左上角位置而言的距离,如下图所示:

其返回值为对象,包括top,right,bottom,left,width,height,x,y几个属性,可以分别获得这些属性的值,这些值都是经过浏览器渲染后的结果。其中top,right,bottom,left可以兼容所有浏览器,而width,height在IE8及以下版本不能正确获得。


运行结果如下: