组件嵌套
组件化的核心思想应该是对组件进行拆分,拆分成一个个小的组件,再将这些组件组合嵌套在一起,最终形成我们的应用程序;
我们来分析一下下面代码的嵌套逻辑:
import React, { Component } from 'react';
function Header() {
return <h2>Header</h2>
}
function Main() {
return (
<div>
<Banner/>
<ProductList/>
</div>
)
}
function Banner() {
return <div>Banner</div>
}
function ProductList() {
return (
<ul>
<li>商品1</li>
<li>商品2</li>
<li>商品3</li>
<li>商品4</li>
<li>商品5</li>
</ul>
)
}
function Footer() {
return <h2>Footer</h2>
}
export default class App extends Component {
render() {
return (
<div>
<Header/>
<Main/>
<Footer/>
</div>
)
}
}
- App组件是Header、Main、Footer组件的父组件;
- Main组件是Banner、ProductList组件的父组件;
在开发过程中,我们会经常遇到需要组件之间相互进行通信:
- 比如App可能使用了多个Header,每个地方的Header展示的内容不同,那么我们就需要使用者传递给Header一些数据,让其进行展示;
- 又比如我们在Main中一次性请求了Banner数据和ProductList数据,那么就需要传递给他们来进行展示;
- 也可能是子组件中发生了事件,需要由父组件来完成某些操作,那就需要子组件向父组件传递事件;
总之,在一个React项目中,组件之间的通信是非常重要的环节;
父组件在展示子组件,可能会传递一些数据给子组件:
- 父组件通过 属性=值 的形式来传递给子组件数据;
- 子组件通过 props 参数获取父组件传递过来的数据;
子组件是class组件
import React, { Component } from 'react';
// 1.类子组件
class ChildCpn1 extends Component {
constructor(props) {
super();
this.props = props;
}
render() {
const { name, age, height } = this.props;
return (
<div>
<h2>我是class的组件</h2>
<p>展示父组件传递过来的数据: {name + " " + age + " " + height}</p>
</div>
)
}
}
export default class App extends Component {
render() {
return (
<div>
<ChildCpn1 name="why" age="18" height="1.88" />
</div>
)
}
}
按照上面的结构,我们每一个子组件都需要写构造器来完成:this.props = props;
其实呢,大可不必,因为我们可以调用super(props),我们来看一下Component的源码:
function Component(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
this.refs = emptyObject;
// We initialize the default updater but the real one gets injected by the
// renderer.
this.updater = updater || ReactNoopUpdateQueue;
}
所以我们的构造方法可以换成下面的写法:
constructor(props) {
super(props);
}
子组件是function组件
functional组件相对来说比较简单,因为不需要有构造方法,也不需要有this的问题。
function ChildCpn2(props) {
const {name, age, height} = props;
return (
<div>
<h2>我是function的组件</h2>
<p>展示父组件传递过来的数据: {name + " " + age + " " + height}</p>
</div>
)
}
export default class App extends Component {
render() {
return (
<div>
<ChildCpn1 name="why" age="18" height="1.88"/>
<ChildCpn2 name="kobe" age="30" height="1.98"/>
</div>
)
}
}
参数验证propTypes
对于传递给子组件的数据,有时候我们可能希望进行验证,特别是对于大型项目来说:
- 当然,如果你项目中默认集成了Flow或者TypeScript,那么直接就可以进行类型验证;
- 但是,即使我们没有使用Flow或者TypeScript,也可以通过 prop-types 库来进行参数验证;
从 React v15.5 开始,React.PropTypes 已移入另一个包中:prop-types 库
import PropTypes from 'prop-types';
对之前的class组件进行验证
ChildCpn1.propTypes = {
name: PropTypes.string,
age: PropTypes.number,
height: PropTypes.number
}
在上面的验证过程中就会有警告发出
更多的验证方式,可以参考官网:https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html
如果没有传递,我们希望有默认值呢?
- 我们使用defaultProps就可以了
import React, { Component } from 'react';
import PropTypes from 'prop-types';
function ChildCpn(props) {
const { name, age, height } = props;
console.log(name, age, height);
const { names } = props;
return (
<div>
<h2>{name + age + height}</h2>
<ul>
{
names.map((item, index) => {
return <li>{item}</li>
})
}
</ul>
</div>
)
}
ChildCpn.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
height: PropTypes.number,
names: PropTypes.array
}
//默认
ChildCpn.defaultProps = {
name: "why",
age: 30,
height: 1.98,
names: ["aaa", "bbb"]
}
export default class App extends Component {
render() {
return (
<div>
<ChildCpn name="why" age={18} height={1.88} names={["abc", "cba"]}/>
<ChildCpn name="kobe" age={40} height={1.98} names={["nba", "mba"]}/>
<ChildCpn/>
</div>
)
}
}
子组件传递父组件
某些情况,我们也需要子组件向父组件传递消息:
- React中同样是通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可;
我们这里来完成一个案例:
- 将计数器案例进行拆解;
- 将按钮封装到子组件中:CounterButton;
- CounterButton发生点击事件,将内容传递到父组件中,修改counter的值;
import React, { Component } from 'react';
function CounterButton(props) {
const { operator, btnClick } = props;
return <button onClick={btnClick}>{operator}</button>
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
changeCounter(count) {
this.setState({
counter: this.state.counter + count
})
}
render() {
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<CounterButton operator="+1" btnClick={e => this.changeCounter(1)} />
<CounterButton operator="-1" btnClick={e => this.changeCounter(-1)} />
</div>
)
}
}