第二章 设计高质量的React组件
- 设计原则
高内聚:把逻辑紧密相关的内容放在一个组件在
低耦合:不同组件之间的依赖关系要尽量弱化,也就是说每个组件要尽量独立
差劲的程序员操心代码,优秀的程序员数据结构和它们之间的关系
——Linus Torvalds, Linux创始人
- prop 和 state
- prop用于定义外部接口,state用于记录内部状态;
- prop的赋值在外部世界使用组件时,state的赋值在组件内部;
- 组件不应该改变prop的值,而state存在的目的就是让组件来改变的。
传递prop
import React, { Component } from "react";
import Counter from "./Counter.js";
// 定义样式
const style = {
margin: "20px"
};
class ControlPanel extends Component {
render() {
console.log("enter ControlPanel render");
return (
// 使用样式
{/* 传递prop */}
);
}
}
export default ControlPanel;
- prop初始化和类型定义
import React, { Component, PropTypes } from "react";
const buttonStyle = {
margin: "10px"
};
class Counter extends Component {
constructor(props) {
console.log("enter constructor: " + props.caption);
super(props);
this.onClickIncrementButton = this.onClickIncrementButton.bind(this);
this.onClickDecrementButton = this.onClickDecrementButton.bind(this);
// 定义state
this.state = {
count: props.initValue
};
}
/* 已废弃的用法
getInitialState() {
console.log('enter getInitialState');
}
getDefaultProps() {
console.log('enter getDefaultProps');
}
*/
render() {
console.log("enter render " + this.props.caption);
const { caption } = this.props;
return (
{caption} count: {this.state.count}
);
}
}
// 定义prop的类型以及是否为必须参数
Counter.propTypes = {
caption: PropTypes.string.isRequired,
initValue: PropTypes.number
};
// 定义默认的prop值
Counter.defaultProps = {
initValue: 0
};
export default Counter;
- 生命周期
React有如下三个过程:
-
装载过程(Mount),也就是把组件第一次在DOM树中渲染的过程;
-
更新过程(Update),当组件被重新渲染的过程;
-
卸载过程(Unmount),组件从DOM中删除的过程。
具体生命周期函数如下:
• constructor(props) - 它在组件初始化时被调用。在这个方法中,你可以设置初始化状态以及绑定类方法。
• componentWillMount() - 它在 render() 方法之前被调用。这就是为什么它可以用作去设置组件内部的状态,因为它不会触发组件的再次渲染。但一般来说,还是推荐在 constructor() 中去初始化状态。
• render() - 这个生命周期方法是必须有的,它返回作为组件输出的元素。这个方法应该是一个纯函数,因此不应该在这个方法中修改组件的状态。它把属性和状态作为输入并且返回(需要渲染的)元素。
• componentDidMount() - 它仅在组件挂载后执行一次。这是发起异步请求去 API 获取数据的绝佳时期。获取到的数据将被保存在内部组件的状态中然后在 render() 生命周期方法中展示出来。
• componentWillReceiveProps(nextProps)-这个方法在一个更新生命周(updatelifecycle)中被调用。新的属性会作为它的输入。因此你可以利用 this.props 来对比之后的属性和之前的属性,基于对比的结果去实现不同的行为。此外,你可以基于新的属性来设置组件的状态。
• shouldComponentUpdate(nextProps, nextState) - 每次组件因为状态或者属性更改而更新时,它都会被调用。你将在成熟的 React 应用中使用它来进行性能优化。在一个更新生命周期中,组件及其子组件将根据该方法返回的布尔值来决定是否重新渲染。这样你可以阻止组件的渲染生命周期(render lifecycle)方法,避免不必要的渲染。
• componentWillUpdate(nextProps, nextState) - 这个方法是 render() 执行之前的最后一个方法。你已经拥有下一个属性和状态,它们可以在这个方法中任由你处置。你可以利用这个方法在渲染之前进行最后的准备。注意在这个生命周期方法中你不能再触发 setState() 。如果你想基于新的属性计算状态,你必须利用componentWillReceiveProps() 。
• componentDidUpdate(prevProps, prevState) - 这个方法在 render() 之后立即调用。你可以用它当成操作 DOM 或者执行更多异步请求的机会。
• componentWillUnmount() - 它会在组件销毁之前被调用。你可以利用这个生命周期方法去执行任何清理任务。
import React, { Component, PropTypes } from 'react';
const buttonStyle = {
margin: '10px'
};
class Counter extends Component {
constructor(props) {
console.log('enter constructor: ' + props.caption);
super(props);
this.onClickIncrementButton = this.onClickIncrementButton.bind(this);
this.onClickDecrementButton = this.onClickDecrementButton.bind(this);
this.state = {
count: props.initValue
};
}
/*
getInitialState() {
console.log('enter getInitialState');
}
getDefaultProps() {
console.log('enter getDefaultProps');
}
*/
componentWillReceiveProps(nextProps) {
console.log('enter componentWillReceiveProps ' + this.props.caption);
}
componentWillMount() {
console.log('enter componentWillMount ' + this.props.caption);
}
componentDidMount() {
console.log('enter componentDidMount ' + this.props.caption);
}
onClickIncrementButton() {
this.setState({ count: this.state.count + 1 });
}
onClickDecrementButton() {
this.setState({ count: this.state.count - 1 });
}
// 是否需要更新渲染,性能优化
shouldComponentUpdate(nextProps, nextState) {
return (
nextProps.caption !== this.props.caption ||
nextState.count !== this.state.count
);
}
render() {
console.log('enter render ' + this.props.caption);
const { caption } = this.props;
return (
{caption} count: {this.state.count}
);
}
}
Counter.propTypes = {
caption: PropTypes.string.isRequired,
initValue: PropTypes.number
};
Counter.defaultProps = {
initValue: 0
};
export default Counter;
- 组件向外传递数据
父组件向子组件传递一个函数,子组件调用函数并传参达到通知父组件的目的,父组件执行函数改变自身的值。
父组件:
import React, { Component } from 'react';
import Counter from './Counter.js';
const style = {
margin: '20px'
};
class ControlPanel extends Component {
constructor(props) {
super(props);
this.onCounterUpdate = this.onCounterUpdate.bind(this);
this.initValues = [ 0, 10, 20];
const initSum = this.initValues.reduce((a, b) => a+b, 0);
this.state = {
sum: initSum
};
}
onCounterUpdate(newValue, previousValue) {
const valueChange = newValue - previousValue;
this.setState({ sum: this.state.sum + valueChange});
}
render() {
return (
Total Count: {this.state.sum}
);
}
}
export default ControlPanel;
子组件:
import React, { Component, PropTypes } from 'react';
const buttonStyle = {
margin: '10px'
};
class Counter extends Component {
constructor(props) {
super(props);
this.onClickIncrementButton = this.onClickIncrementButton.bind(this);
this.onClickDecrementButton = this.onClickDecrementButton.bind(this);
this.state = {
count: props.initValue
}
}
onClickIncrementButton() {
this.updateCount(true);
}
onClickDecrementButton() {
this.updateCount(false);
}
updateCount(isIncrement) {
const previousValue = this.state.count;
const newValue = isIncrement ? previousValue + 1 : previousValue - 1;
this.setState({count: newValue})
this.props.onUpdate(newValue, previousValue)
}
render() {
const {caption} = this.props;
return (
{caption} count: {this.state.count}
);
}
}
Counter.propTypes = {
caption: PropTypes.string.isRequired,
initValue: PropTypes.number,
onUpdate: PropTypes.func
};
Counter.defaultProps = {
initValue: 0,
onUpdate: f => f //什么都不做的函数
};
export default Counter;