我正在参加「掘金·启航计划」
一、父组件向子组件传递数据
基本使用方法
父组件在展示子组件的时候,有时需要向子组件中传递数据:
父组件可通过 属性=值 的形式来向子组件传递数据
子组件通过 constructor 中的 props 参数来接收父组件传递过来的数据
比如在Main组件中使用了 Banner子组件及Header子组件等,在Main组件中获取到banner列表后传递给Banner组件,在Banner组件中进行展示:
Main.jsx
import React, { Component } from 'react'
import axios from "axios"
import MainBanner from './MainBanner'
import MainHeader from './MainHeader'
export class Main extends Component {
constructor() {
super()
this.state = {
banners: [],
}
}
componentDidMount() {
axios.get("http://123..").then(res => {
const banners = res.data.data.banner.list
this.setState({
banners,
})
})
}
render() {
const { banners } = this.state
return (
<div className='main'>
<div>Main</div>
<MainBanner banners={banners} title="轮播图"/>
<MainHeader />
</div>
)
}
}
export default Main
MainBanner.jsx:
import React, { Component } from 'react'
import PropTypes from "prop-types"
export class MainBanner extends Component {
// 如果子组件中没有state,可以省略constructor
// 因为组件中的constructor会自动将props放在组件实例上
constructor(props) {
super(props)
}
render() {
const { title, banners } = this.props
return (
<div className='banner'>
<h2>封装一个轮播图: {title}</h2>
<ul>
{
banners.map(item => {
return <li key={item.acm}>{item.title}</li>
})
}
</ul>
</div>
)
}
}
export default MainBanner
父组件传递过来的数据会被子组件的props接收,如果子组件不需要定义自己state则可以省略constructor
组件中的constructor默认会通过super(props)将props放到组件实例上
在子组件中可以通过this.props.xx来获取父组件中传递的数据
如果在父组件中想向子组件中传递多个属性,可以通过展开运算符的方式来传递:
import React, { Component } from 'react'
import Home from './Home'
export class App extends Component {
constructor() {
super()
this.state = {
info: { name: "zs", age: 18 }
}
}
render() {
const { info } = this.state
return (
<div>
{/* 给Home传递数据 */}
{/* <Home name={info.name} age={info.age}/> */}
<Home {...info}/>
</div>
)
}
}
export default App
PropTypes
对于传递给子组件的数据,有时候我们想要进行验证(尤其是对于大型项目来说):
- 如果项目中使用了TypeScript,便可以直接进行类型验证
- 但如果没有使用TypeScript,需要通过prop-types库来进行参数验证
比如可以验证数组,数组中包含哪些元素;
比如可以验证对象,对象中包含哪些key及value是什么类型;
比如可以确保没有传递某些数据时打印出警告信息;
也可以设置传入数据的默认值;
更多验证方式可参考官方文档:zh-hans.reactjs.org/docs/typech…
import React, { Component } from 'react'
import PropTypes from "prop-types"
export class MainBanner extends Component {
//...
}
// MainBanner传入的props类型进行验证
MainBanner.propTypes = {
banners: PropTypes.array,
title: PropTypes.string,
// 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
// 这个 prop 没有被提供时,会打印警告信息。
requiredFunc: PropTypes.func.isRequired,
}
// MainBanner传入的props的默认值
MainBanner.defaultProps = {
banners: [],
title: "默认标题"
}
export default MainBanner
import PropTypes from 'prop-types';
MyComponent.propTypes = {
// 你可以将属性声明为 JS 原生类型,默认情况下
// 这些属性都是可选的。
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// 任何可被渲染的元素(包括数字、字符串、元素或数组)
// (或 Fragment) 也包含这些类型。
optionalNode: PropTypes.node,
// 一个 React 元素。
optionalElement: PropTypes.element,
// 一个 React 元素类型(即,MyComponent)。
optionalElementType: PropTypes.elementType,
// 你也可以声明 prop 为类的实例,这里使用
// JS 的 instanceof 操作符。
optionalMessage: PropTypes.instanceOf(Message),
// 你可以让你的 prop 只能是特定的值,指定它为
// 枚举类型。
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
// 一个对象可以是几种类型中的任意一个类型
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 可以指定一个数组由某一类型的元素组成
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
// 可以指定一个对象由某一类型的值组成
optionalObjectOf: PropTypes.objectOf(PropTypes.number),
// 可以指定一个对象由特定的类型值组成
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
}),
// 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
// 这个 prop 没有被提供时,会打印警告信息。
requiredFunc: PropTypes.func.isRequired,
// 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
// 请不要使用 `console.warn` 或抛出异常,因为这在 `oneOfType` 中不会起作用。
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},
// 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。
// 它应该在验证失败时返回一个 Error 对象。
// 验证器将验证数组或对象中的每个值。验证器的前两个参数
// 第一个是数组或对象本身
// 第二个是他们当前的键。
customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
};
二、子组件向父组件传递消息
某些情况下,也需要子组件向父组件传递消息:
- Vue中是通过自定义事件完成的
- 而在React中,同样是通过props来传递:父组件给子组件传递一个回调函数,子组件去调用这个函数从而实现子组件向父组件传递消息
案例
比如,在一个父组件中包含两个子组件,分别进行加数字与减数字的操作;而这个数字是在父组件中维护的,此时就要通过props向这两个子组件中传递回调函数来实现对父组件中数字的加、减:
App.jsx:
import React, { Component } from 'react'
import AddCounter from './AddCounter'
import SubCounter from './SubCounter'
export class App extends Component {
constructor() {
super()
this.state = {
counter: 100
}
}
changeCounter(count) {
this.setState({counter: this.state.counter + count})
}
render() {
const { counter } = this.state
return (
<div>
<div>{counter}</div>
<AddCounter addCounter={(count) => this.changeCounter(count)}/>
<SubCounter subCounter={(count) => this.changeCounter(count)}/>
</div>
)
}
}
export default App
AddCounter.jsx:
import React, { Component } from 'react'
export class AddCounter extends Component {
addCount(count) {
this.props.addCounter(count)
}
render() {
return (
<div>
<button onClick={() => this.addCount(1)}>Add 1</button>
<button onClick={() => this.addCount(5)}>Add 5</button>
<button onClick={() => this.addCount(10)}>Add 10</button>
</div>
)
}
}
export default AddCounter
SubCounter.jsx:
import React, { Component } from 'react'
export class SubCounter extends Component {
subCount(count) {
this.props.subCounter(count)
}
render() {
return (
<div>
<button onClick={() => this.subCount(-1)}>Sub 1</button>
<button onClick={() => this.subCount(-5)}>Sub 5</button>
<button onClick={() => this.subCount(-10)}>Sub 10</button>
</div>
)
}
}
export default SubCounter
三、组件间通信案例
父组件中引入子组件TabControl,子组件TabControl中根据父组件传递的列表来进行渲染选择页签
- 当点击不同页签时,页签高亮
- 同时父组件中渲染出页签对应的内容
App.jsx:
import React, { Component } from 'react'
import TabControl from './TabControl'
export class App extends Component {
constructor() {
super()
this.state = {
titles: ['最新', '热门', '精选'],
tabIndex: 0
}
}
changeIndex(index) {
this.setState({
tabIndex: index
})
}
render() {
const {titles, tabIndex} = this.state
return (
<div>
<TabControl titles={titles} tabClick={(index) => this.changeIndex(index)}/>
{ titles[tabIndex] }
</div>
)
}
}
export default App
TabControl/index.jsx:
import React, { Component } from 'react'
import "./style.css"
export class TabControl extends Component {
constructor() {
super()
this.state = {
currentIndex: 0
}
}
changeTab(index) {
this.setState({currentIndex: index})
this.props.tabClick(index)
}
render() {
const { currentIndex } = this.state
const { titles } = this.props
return (
<div className='tab-control'>
{
titles.map((tab, index) => {
return (
<div
className={`item ${currentIndex === index ? 'active':''}`}
key={tab}
onClick={() => this.changeTab(index)}
>
<span className="text">{tab}</span>
</div>
)
})
}
</div>
)
}
}
export default TabControl
style.css:
.tab-control {
display: flex;
align-items: center;
height: 40px;
text-align: center;
}
.tab-control .item {
flex: 1;
}
.tab-control .item.active {
color: red;
}
.tab-control .item.active .text {
padding: 3px;
border-bottom: 3px solid red;
}