组件通信 Props
React 组件使用 props 来互相通信。每个父组件都可以提供 props 给它的子组件,从而将一些信息传递给它。Props 可能会让你想起 HTML 属性,但你可以通过它们传递任何 JavaScript 值,包括对象、数组和函数 以及 html 元素,这样可以使我们的组件更加灵活。
例如我们在使用原生 html 标签时,我们可以为其传递属性,如下
<img width="500" height="500" alt="xxx" src="xxxxxxx" />
那在 React 中,也允许将属性传递给自己编写的组件
如下
export default function App() {
return <Card title="标题1" content="内容"></Card>;
}
父子组件通信
编写一个子组件
Test
const Test = () => {
return <div>Test</div>;
};
export default Test;
在 App.tsx 引入该子组件
import Test from './components/Test';
function App() {
return (
<>
<Test></Test>
</>
);
}
export default App;
1. 父向子组件传递 props
支持的类型如下:
- string
title={'测试'}
- number
id={1}
- boolean
isGirl={false}
- null
empty={null}
- undefined
empty={undefined}
- object
obj={ { a: 1, b: 2 } }
- array
arr={[1, 2, 3]}
- function
cb={(a: number, b: number) => a + b}
- JSX.Element
element={<div>测试</div>}
function App() {
return (
<>
<Test
title={'测试'}
id={1}
obj={{ a: 1, b: 2 }}
arr={[1, 2, 3]}
cb={(a: number, b: number) => a + b}
empty={null}
element={<div>测试</div>}
isGirl={false}
></Test>
</>
);
}
子组件接受父组件传递的 props
props 是一个对象,会作为函数的第一个参数接受传过来的 props 值
注意:我们需要遵守单向数据流,子组件不能直接修改父组件的props
在 React 源码中会使用Object.freeze
冻结 props,限制 props 的修改。
Object.freeze() 静态方法可以使一个对象被冻结。冻结对象可以防止扩展,并使现有的属性不可写入和不可配置。被冻结的对象不能再被更改:不能添加新的属性,不能移除现有的属性,不能更改它们的可枚举性、可配置性、可写性或值,对象的原型也不能被重新指定
import React from 'react';
interface Props {
title: string;
id: number;
obj: {
a: number;
b: number;
};
arr: number[];
cb: (a: number, b: number) => number;
empty: null;
element: JSX.Element;
}
const Test: React.FC<Props> = props => {
console.log(props);
return <div>Test</div>;
};
export default Test;
2.定义默认值
第一种方式
将属性变为可选的这儿使用title
举例 title?: string
然后将 props 进行解构,定义默认值 {title = '默认标题'}
import React from 'react';
interface Props {
title?: string;
id: number;
obj: {
a: number;
b: number;
};
arr: number[];
cb: (a: number, b: number) => number;
empty: null;
element: JSX.Element;
}
const Test: React.FC<Props> = ({ title = '默认标题' }) => {
return <div>Test</div>;
};
export default Test;
第二种方式
使用defaultProps
进行默认值赋值,最后把 defaultProps 和 props 合并,注意顺序要先写 defaultProps,再写 props 因为 props 会覆盖 defaultProps 的值。
import React from 'react';
interface Props {
title?: string;
id: number;
obj: {
a: number;
b: number;
};
arr: number[];
cb: (a: number, b: number) => number;
empty: null;
element: JSX.Element;
}
const defaultProps: Partial<Props> = {
title: '默认标题',
};
const Test: React.FC<Props> = props => {
const { title } = { ...defaultProps, ...props };
return <div>{title}</div>;
};
export default Test;
3. React.FC
React.FC 是函数式组件,是在 TS 使用的一个范型。FC 是 Function Component 的缩写
React.FC 帮助我们自动推导 Props 的类型。
注意:在旧版本的 React.FC 是包含
PropsWithChildren
这个声明新版本已经没有了
4.props.children 特殊值
这个功能类似于 Vue 的插槽,直接在子组件内部插入标签会自动一个参数props.children
function App() {
return (
<>
<Test>
<div>123</div>
</Test>
</>
);
}
子组件使用 children 属性
在之前的版本 children 是不需要手动定义的,在 18 之后改为需要手动定义类型
这样就会把父级的 <div>123</div>
插入子组件的 <div>
里面
import React from 'react';
interface Props {
children: React.ReactNode; //手动声明children
}
const Test: React.FC<Props> = props => {
return <div>{props.children}</div>;
};
export default Test;
5.子组件给父组件传值
React 没有像 Vue 那样的 emit 派发事件,所有我们回调函数模拟 emit 派发事件
父组件传递函数
过去,其本质就是录用函数的回调
import Test from './components/Test';
function App() {
const fn = (params: string) => {
console.log('子组件触发了 父组件的事件', params);
};
return (
<>
<Test callback={fn}></Test>
</>
);
}
子组件接受函数,并且在对应的事件调用函数,回调参数回去
import React from 'react';
interface Props {
callback: (params: string) => void;
children?: React.ReactNode;
}
const Test: React.FC<Props> = props => {
return (
<div>
<button onClick={() => props.callback('我见过龙')}>派发事件</button>
</div>
);
};
export default Test;
兄弟组件通信
定义两个组件放到一起作为兄弟组件,其原理就是发布订阅
设计模式,发布订阅已经讲过无数次了这里不在阐述,原生浏览器已经实现了这个模式我们可以直接使用。
如果不想使用原生浏览器,可以使用 mitt
mitt 文档 https://www.npmjs.com/package/mitt
import Card from './components/Card';
import Test from './components/Test';
function App() {
return (
<>
<Test></Test>
<Card></Card>
</>
);
}
export default App;
第一个兄弟组件 定义事件模型
import React from 'react';
const Test: React.FC = props => {
const event = new Event('on-card'); //添加到事件中心
const clickTap = () => {
console.log(event);
event.params = { name: '我见过龙' };
window.dispatchEvent(event); //派发事件
};
//此处更推荐这种用法,因为CustomEvent自身支持传递参数
// const event = new CustomEvent('on-card', {
// detail: { name: '我是card,你是谁' }, // 使用 detail 字段传递数据
// });
// window.dispatchEvent(event);
return (
<div>
<button onClick={clickTap}>派发事件</button>
</div>
);
};
//扩充event类型
declare global {
interface Event {
params: any;
}
}
export default Test;
第二个兄弟组件接受事件
import './index.css';
export default function Test2() {
//接受参数
window.addEventListener('on-card', e => {
console.log(e.params, '触发了');
});
return <div className="card"></div>;
}