2.3 组件向外传递数据
现在我们已经知道如何通过 prop 从父组件传递数据给子组件,而组件之间的交流也应该是相互的,子组件某些情况下也需要把数据传递给父组件。
在我们之前的例子中,包含三个计数组件——Counter。每一个 Counter 都有一个可以动态改变的计数值,我们希望父容器能显示三个子组件当前的计数值之和。如图2-8所示。
图2-8 包含总数的效果
这个功能看起来很简单,但是要解决一个问题,就是父容器知道这三个子组件的计数值,而且是每次变更都要立刻知道,而 Counter 组件的当前值组件的内部状态如何让外部世界知道这个值呢?
解决这个问题的方法,依然是利用 prop。组件的 prop 可以是任何 JS 对象,而在 JS 中,函数本身就可以被看做一种对象,既可以像其他对象一样作为 prop 的值从父组件传递给子组件,又可以被子组件作为函数调用,这样事情就好办了。
接下来看实现这个功能的关键代码。 在 Counter 组件中,对于点击 “+” 和 “-” 按钮的事件和输入完成的事件处理方法做了改动,参考代码片段2-3-1。
_checkNumber() {
let value = this.state.value;
console.log(value);
if (value === '' || value < 1) {
value = 1;
} else {
value = Math.floor(value);
}
this._update(value);
}
_reduce() {
let value = this.state.value - 1;
if (value < 1) value = 1;
this._update(value);
}
_plus() {
this._update(this.state.value + 1);
}
_update(value) {
this.props.onUpdate(this.state.value, value);
this.setState({value: value})
}
代码片段 2-3-1
现在,_checkNumber(),_reduce(),_plus() 三个函数计算得到 value 值,为了避免重复代码,重构了下代码,把修改状态机的代码提取到了 _update() 函数,在修改状态机的值之前调用 this.props.onUpdate(this.state.value, value);。 这个函数就用来告诉父组件值的变化,我们先约定第一个参数是变化前的值,第二个参数是变化后的值。
TextInput 组件代码也需要修改,
<TextInput style={styles.inp1}
returnKeyType='done'
maxLength={3}
onEndEditing={this._checkNumber.bind(this)}
value={this.state.value.toString()}
keyboardType="numeric"
onChangeText={(txt) => this._update(Number(txt))}
autoFocus={false}
underlineColorAndroid="transparent">
</TextInput>
onChangeText 回调函数也调用 _update()。 由于_update() 函数里使用了 this 关键字,按照 ES6 语法需要对这个函数进行绑定。
// 构造
constructor(props) {
super(props);
//...
this._update = this._update.bind(this);
}
_update() 函数里调用了this.props.onUpdate 对应 Counter 组件的 propTypes 和 defaultProps 就要增加 onUpdate 的定义。
export default class Counter extends Component {
// 默认属性
static defaultProps = {
initValue: 1,
onUpdate: f => f // 默认是一个什么都不做的函数。
};
}
Counter.propTypes = {
initValue: PropTypes.number,
style: PropTypes.object,
onUpdate: PropTypes.func
};
新增加的 prop 叫做 onUpdate ,类型是一个函数,当 Counter 的状态改变的时候,就会调用这个给定的函数,从而达到通知父组件的作用。onUpdate 函数里的两个值就是子组件传递给父组件的数据,根据传递的数据,父组件可以推导出数值是增加还是建设。如何使用这两个值呢?我们来看下父组件里代码,参考代码片段2-3-2
type Props = {};
// ES6 语法
export default class App extends Component<Props> {
constructor(props) {
super(props);
this.initValues = [1, 2, 3];
const initSum = this.initValues.reduce((a, b) => a + b, 0);
this.state = {
sum: initSum
};
this.onUpdate=this.onUpdate.bind(this)
}
render() {
return ( // 渲染布局
<View style={styles.container}>
<Text style={{margin:10,fontSize:20,color:'black'}}>总计 {this.state.sum}</Text>
<Counter style={{margin: 10}} onUpdate={this.onUpdate} initValue={this.initValues[0]}/>
<Counter style={{margin: 10}} onUpdate={this.onUpdate} initValue={this.initValues[1]}/>
<Counter style={{margin: 10}} onUpdate={this.onUpdate} initValue={this.initValues[2]}/>
</View>
);
}
onUpdate(oldCounter,newCounter){
const valueChange=newCounter-oldCounter;
this.setState({sum:this.state.sum+valueChange})
}
}
代码片段2-3-2
在父组件第一次渲染的时候,就需要显示三个计数器数值的总和,所以我们在构造函数中用 initValues 数组记录所有的 Counter 初始值,在初始化 this.state 之前,将 initValues 数组中所有值加在一起,作为状态机变量 sum 字段的初始值。
传递给 Counter 组件的回调函数 onUpdate 里使用了子组件传递过的数据,第一个参数是旧值,第二个参数是新值,两者之差就是改变值,将这个改变作用到 this.state.sum 上就是新的 sum 状态。
虽然 RN 有 PropType 能够检查 prop 的类型,却没有任何机制能来限制 prop 的参数规格,参数的一致性只能靠开发者来保证。
查看代码
如果你已经从 GitHub 上克隆了这个程序的 Git 仓库,那么可以执行 git checkout 2e签出程序的这个版本。