2.3 组件向外传递数据

现在我们已经知道如何通过 prop 从父组件传递数据给子组件,而组件之间的交流也应该是相互的,子组件某些情况下也需要把数据传递给父组件。

在我们之前的例子中,包含三个计数组件——Counter。每一个 Counter 都有一个可以动态改变的计数值,我们希望父容器能显示三个子组件当前的计数值之和。如图2-8所示。 图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签出程序的这个版本。

results matching ""

    No results matching ""