一、组件通信
React中组件分为两种类型:一种是React Dom组件,一种是React组件。
Dom组件指的是React支持的所有的HTML和SVG标签。
function App() {
return (
<div>
<img
src=""
alt=""
>
</img>
</div>
);
}
export default App;
上图为纯HTML的写法,但是在React中,这些属性不是纯HTML写法,它会有一些变化,像上图中HTML属性的功能,在React中称为Props。
1.为DOM组件设置Props
props可以理解为道具,也可以理解为属性,像Properties。
在React Props的使用中,跟HTML属性使用是有一些区别的。
如以下例子:
import image from './logo.svg'
function App() {
return (
<div>
<img
src={image}
alt=""
>
</img>
</div>
);
}
export default App;
1.1 DOM组件的类属性
类属性也就是CSS类名,类名的名称叫做class,但由于在React中使用的是JSX语法,所以它可能会牵扯到跟JS关键字class重名的情况,在React中进行类名设置的话需要更改为另外一个名字叫做className。
import image from './logo.svg'
function App() {
return (
<div>
<img
src={image}
alt=""
className="small"
>
</img>
</div>
);
}
export default App;
但如果在React直接设置class会直接报错。
1.2 DOM组件的Style属性
在React使用JSX的时候,由于style是一种键值的结构,所以JSX允许你通过键值对,就是对象的形式来进行style设置。
import image from './logo.svg'
function App() {
return (
<div>
<img
src={image}
alt=""
className="small"
style={{
width:'200vh',
height:200,
backgroundColor:'grey'
}}
>
</img>
</div>
);
}
export default App;
也可以书写变量放入style大括号中
import image from './logo.svg'
function App() {
const imgStyleObj={
width:'200vh',
height:200,
backgroundColor:'grey'
}
return (
<div>
<img
src={image}
alt=""
className="small"
style={imgStyleObj}
>
</img>
</div>
);
}
export default App;
1.3 JSX的展开语法
如果我们给一个标签进行过多不必须的属性设置的话,我们这样写是比较繁琐的,所以JSX还支持一种展开语法。
import image from './logo.svg'
function App() {
const imgData={
className:"small",
style:{
width:'200vh',
height:200,
backgroundColor:'grey'
}
}
return (
<div>
<img
src={image}
alt=""
{...imgData}
>
</img>
</div>
);
}
export default App;
小贴士:JSX的展开操作并不是ES6的展开运算符。
它的功能是将当前对象的键值直接放到这个位置上,它是不能再一个没有容器的地方单独使用的。它是相当于把当前对象的属性拿出来,又放在一个对象字面量中组成了一个新的对象,也是我们对象进行拷贝的一种方案。
但在React中标签里大括号只是JSX的语法标记,就算去掉大括号也不可能直接放在img标签中,所以这块只能大括号处理,是JSX单独的功能支持。
2.React组件的Props
我们自定义的React组件
可以实现组件复用,例子如下
function Article (params) {
return (
<div>
<h2>标题1</h2>
<p>内容</p>
</div>
)
}
export default function App() {
return (
<>
<Article />
<Article />
<Article />
</>
)
}
不过会有另外一个问题,当进行组件复用的时候,通常只是我们的结构样式逻辑进行复用,内容是不可能复用的,所以我们希望对组件的一些内容进行一些定制。
操作步骤:
1.请求功能所需的数据(例如文章信息)
2.创建Article组件
3.将文章的数据分别传递给Article
采用的方式就是通过props,我们既可以给DOM组件传递props来进行标签功能设置,也可以给React组件进行数据传递,这也是组件通信中父组件向子组件进行传值的一种方式。
function Article ({title,content,active}) {
return (
<div>
<h2>{title}</h2>
<p>{content}</p>
<p>状态:{active?'显示中':'隐藏中'}</p>
</div>
)
}
export default function App() {
return (
<>
<Article title="标题1" content="内容1" active/>
<Article title="标题2" content="内容2"/>
<Article title="标题3" content="内容3" active/>
</>
)
}
2.1 在React组件中展开Props的使用场景
可能文章内容会有更多的不同个性化细节,比如标题的区域,作者的信息,发布时间等等,内容区域可能会有像图像,各种区域的展示(基于多种子组件),也有可能将标题和内容区域做单独的封装,因为内容区域是一个文本呈现,或者需要其他的功能组件来参与,这时候固定就不太方便了,就需要再嵌套一个Detail组件处理,就需要展开Props。
function Detail({content,active}) {
return (
<>
<p>{content}</p>
<p>状态:{active?'显示中':'隐藏中'}</p>
</>
)
}
function Article ({title,detailData}) {
return (
<div>
<h2>{title}</h2>
<Detail {...detailData} />
</div>
)
}
export default function App() {
const articleData={
title:'标题1',
detailData:{
content:'内容1',
active:true
}
}
return (
<>
<Article {...articleData}/>
</>
)
}
3.将JSX作为Props传递(组件插槽)
组件插槽是在组件中定义的具有特定名称的标记,允许用户在组件使用时插入自定义内容。组件插槽允许用户通过将自定义内容嵌入到组件中来更改组件的外观和行为,从而提高组件的灵活性和可重用性。组件插槽在构建可复用组件库时非常有用,可以帮助开发人员提高代码复用性。
function List({children}) {
return (
<ul>
{children}
</ul>
)
}
export default function App() {
return (
<>
<List>
<li>列表项1</li>
<li>列表项2</li>
<li>列表项3</li>
</List>
<List>
<li>列表项4</li>
<li>列表项5</li>
<li>列表项6</li>
</List>
</>
)
}
利用JSX语法实现了所谓的一个叫插槽的概念,所以在react中,你几乎看不到关于插槽这两个字,因为它就是使用了JSX语法,所以React中讲插槽功能称为将JSX作为props传递给子组件,它和普通prop的区别在于它是被预定义了一个属性叫做children。
3.1 向多个位置传递JSX
除了希望向某一个地方进行插入,还希望在列表顶部或者底部在做一个内容呈现。
function List({children,title,footer=<div>默认底部</div>}) {
return (
<>
<h2>{title}</h2>
<ul>
{children}
</ul>
{footer}
</>
)
}
function Li({children}) {
const listContent = children.map(item=>
<li key={item.id}>{item.content}</li>
)
return (
<>
{listContent}
</>
)
}
export default function App() {
const listData=[
{
id:1,
title:'列表1',
footer:<p>这是底部内容1</p>,
listContent:[
{
id:1,content:'内容1'
},
{
id:2,content:'内容2'
},
{
id:3,content:'内容3'
}
]
},
{
id:2,
title:'列表2',
footer:<p>这是底部内容2</p>,
listContent:[
{
id:1,content:'内容A'
},
{
id:2,content:'内容B'
},
{
id:3,content:'内容C'
}
]
},
{
id:3,
title:'列表3',
// footer:<p>这是底部内容2</p>,
listContent:[
{
id:1,content:'内容X'
},
{
id:2,content:'内容Y'
},
{
id:3,content:'内容Z'
}
]
}
]
const list = listData.map(item=>
<List title={item.title} footer={item.footer}>
<Li children={item.listContent}></Li>
</List>
)
return (
<>
{list}
</>
)
// return (
// <>
// <List title="列表1" footer={<p>这是底部内容1</p>}>
// <li>内容1</li>
// <li>内容2</li>
// <li>内容3</li>
// </List>
// <List title="列表2" footer={<p>这是底部内容2</p>}>
// <li>内容A</li>
// <li>内容B</li>
// <li>内容C</li>
// </List>
// <List title="列表3">
// <li>内容X</li>
// <li>内容Y</li>
// <li>内容Z</li>
// </List>
// </>
// )
}
最后props的传值都是单向的,而且父组件向子组件传的值都是只读的。
4.子组件向父组件传值
需要父组件向子组件进行一个自定义事件的设置,然后通过事件触发后,向我们父组件传递参数的方式来进行实现。
import { useState } from "react";
function Detail({onActive}) {
const [status,setStautus]=useState(false)
function handeClick(){
setStautus(!status)
onActive(status)
}
return(
<div>
<button onClick={handeClick}>按钮</button>
<p style={{display:status?'block':'none'}}>Detail的内容</p>
</div>
)
}
function App() {
function handleActive(status) {
console.log(status);
}
return (
<>
<Detail onActive={handleActive}/>
</>
);
}
export default App;
5.使用Context进行多级组件传值
如果组件层级比较多,要进行数据传递的话,那肯定会经历一级一级数据传递,prop用起来就比较麻烦,我们就可以利用一个React中的Hooks Context进行操作。
import { createContext,useContext } from "react"
const LevelContext = createContext(0)
function Section({children}) {
const level = useContext(LevelContext)
return (
<section >
<LevelContext.Provider value={level + 1}>
{children}
</LevelContext.Provider>
</section>
)
}
function Heading({children}) {
const level = useContext(LevelContext)
switch(level){
case 1:
return <h1>{children}</h1>
case 2:
return <h2>{children}</h2>
case 3:
return <h3>{children}</h3>
case 4:
return <h4>{children}</h4>
case 5:
return <h5>{children}</h5>
case 6:
return <h6>{children}</h6>
default:
throw Error('未知的 level:'+level)
}
}
export default function App() {
return (
<div>
<Section>
<Heading >主标题</Heading>
<Section>
<Heading >副标题</Heading>
<Heading >副标题</Heading>
<Heading >副标题</Heading>
<Section>
<Heading >子标题</Heading>
<Heading >子标题</Heading>
<Heading >子标题</Heading>
<Section>
<Heading >子子标题</Heading>
<Heading >子子标题</Heading>
<Heading >子子标题</Heading>
</Section>
</Section>
</Section>
</Section>
</div>
);
}