少踩点坑,你值得知道的React事件绑定

写在前面

之前写Vue写惯了,心血来潮,写起了react。javascript

github地址:Close2Reactvue

项目使用框架版本主要有 react(15.4.1) + react-dom(15.4.1) + webpack(1.13.3) + axios(0.15.3) + node(6.2.2), 点击查看项目简介:一段人人都应该知道的从 vue 到 react 的过渡史java

目前该项目有两个分支, half-es6 + masternode

half-es6和master实现的功能同样, 实现了CURD + Axios + Othersreact

half-es6的写法并无彻底使用es6的class的概念, master是完善了它webpack

如今让咱们快速了解React的事件绑定都有什么坑~
⬇️ ⬇️ ⬇️ios

案例1:

tab示例效果图

错误示范

// 父组件主要是为了实现tab的切换
const Content = React.createClass({
    getInitialState() {
        return {
            tabTxt: ['CURD', 'Axios', 'Others'],
            choice: 0, //目前激活的tab的下标
        }
    },
    
    switchChoice(idx){ // 设置choice
        this.setState({
            choice: idx
        })
    },
    
    renderTabInit(text, idx) {
        return (<Tab key={idx} idx={idx}
                      // 利用props的绑定,把switchChoice传到子组件中去,this.props.choose能够调用到这个方法
                     choose={this.switchChoice} 
                     choice={this.state.choice}
        >{text}</Tab>)
    },
    
    render() { ...... }
});

自觉得把方法传入了子组件,就在Tab子组件中直接this.props.choose调用父组件的方法git

const Tab = React.createClass({
    render(){
        return (
            <span className={this.props.idx == this.props.choice? "tab on" : "tab"}
                  data-idx={this.props.idx}
                  // 本意是在点击的时候,可以直接调用父组件的方法
                  onClick={this.props.choose(this.props.idx)} 
            >{this.props.children}</span>
        )
    }
});

结果浏览器打开就爆炸了。boom
es6

大概意思就是说:
我在父组件中的setState在渲染的时候致使了一个错误。React不能更新一个正在变化的state。
组件中的render应该是一个带有state和props的pure function(纯函数)。若是不是纯函数,构造器会产生一些反作用。
好比在render的时候(组件挂载的时候)会根据props指定的参数继续向下执行,则会在挂载的时候(还没发生点击事件)就直接执行了父组件的函数。github

顺便解释一下pure function
一、给出一样的参数值,该函数老是求出一样的结果。该函数结果值不依赖任何隐藏信息或程序执行处理可能改变的状态或在程序的两个不一样的执行,也不能依赖来自I/O装置的任何外部的输入
二、结果的求值不会促使任何可语义上可观察的反作用或输出,例如易变对象的变化或输出到I/O装置

正确姿式

const Tab = React.createClass({
    chooseTab() { // 子组件的中转函数
        this.props.choose(this.props.idx); //在这里调用父组件的函数
    },
    
    render(){
        return (
            <span className={this.props.idx == this.props.choice? "tab on" : "tab"}
                  data-idx={this.props.idx}
                  onClick={this.chooseTab} // 调用中转函数
            >{this.props.children}</span>
        )
    }
});

这个中转函数的名词是我本身取的,只是这样就能让点击事件的函数变成pure function,就不会在组件挂载的时候就沿着props继续向下执行,就能避免在挂载组件的时候就直接调用父组件的setState了。

        • --

案例2

todolist 的 编辑 & 保存 示例效果图

错误示范

// 父组件
const PageA = React.createClass({
    getInitialState() { ... }, // 初始化todolist的数据
    componentDidMount(){ ... }, // 挂载组件时的函数
    initDidCount() { ... }, // 更新完成的进度
    
    handleTxtChange(event){ // 重点: 当input的输入值变化时调用这个函数
        let index = event.target.getAttribute('data-index'); // 强行获得todolist的index
        // 这里必定须要index这个参数做为修改this.state.list时候的下标
        this.state.list[index].text = event.target.value; // 把input的值更新到state上去
        this.setState({
            list: this.state.list
        });
        this.initDidCount(); // 更新完成进度
    },

    handleCheckChange(event,idx) { ... }, // checkbox的onChange,和input的onChange同样
    deleteItem(idx) { ... },  // 删除
    
    initListLi(val,idx) {
        return (
            <List {...val} key={idx} index={idx} // 绑定一些须要用到的props
                 // 利用props的绑定,把handleTxtChange传到子组件中去,
                 //子组件中用this.props.handleTxtChange能够调用到这个方法
                 //(handleCheckChange也是同理)
                  handleTxtChange={this.handleTxtChange}
                  handleCheckChange={this.handleCheckChange}
                  deleteItem={this.deleteItem}
            />
        )
    },
    
    render() { ...... }
});

这里也会和案例1有一样的状况,父组件用props传入的方法里面有setState,若是在子组件的reader中直接用this.props.handleTxtChange 调用的话,会致使函数不纯。

错误姿式1

// 错误的父组件1
...
 handleTxtChange(event,idx){ // 重点:【错误写法1】 强行传了两个参数
        console.log(event, idx); // 在控制台上输出结果
            this.state.list[idx].text = event.target.value; // 把input的值更新到state上去
            this.setState({
                list: this.state.list
            });
            this.initDidCount(); // 更新完成进度
        },
...


// 错误的子组件1
...
render (){
        return (
            <li className="li">
                ...
                    {
                    this.state.status?
                        // 重点代码开始
                        <input type="text" className="ipt"
                               defaultValue={this.props.text}
                               //【错误写法1】 直接调用了父组件的函数,而且直接传了两个参数(框架的默认参数event和自定义参数index)
                                  onChange={this.props.handleTxtChange(event,this.props.index)}/>:    
                       // 重点代码结束
                        <p className="p">{this.props.text}</p>
                }
                ...
            </li>
        )
    }
...

你会发现,你想要给props的方法里传的自定义参数index能正常获取,
而框架自带参数event怎么都拿不到,
结果只能以下,event会变成undefined。

错误姿式2

// 错误的父组件2
...
 handleTxtChange(event){ // 重点:【错误写法2】 只有框架自带参数event
        console.log(event.target); // 在控制台上输出结果
         let index = event.target.getAttribute('data-index'); // 强行拿到标签上的自定义属性
            this.state.list[index].text = event.target.value; // 把input的值更新到state上去
            this.setState({
                list: this.state.list
            });
            this.initDidCount(); // 更新完成进度
        },
...


// 错误的子组件2
...
render (){
        return (
            <li className="li">
                ...
                {
                    this.state.status?
                        // 重点代码开始
                        <input type="text" className="ipt"
                               defaultValue={this.props.text}
                               //【错误写法2】 直接调用父组件的函数,可是不传参数
                               // 自定义参数利用自定义属性的方式传入,
                               // 此次尝试,也只是为了可以拿到正确的event
                               data-index={this.props.index} // 强行使用了自定义属性
                               onChange={this.props.handleTxtChange}/>:    // 不带参数
                       // 重点代码结束
                        <p className="p">{this.props.text}</p>
                }
                ...
            </li>
        )
    }
...

当发现多传了参数,致使了框架自带的默认参数event怎么都取不到的时候,
决定不传参数,用其余歪门邪道(好比自定义属性)拿到想要的参数。
在input中输入内容,结果以下。虽然正确,但这样写感受实在是不够智能。

总之,这样写虽然解决了问题,但我仍是以为姿式仍是不对。

正确姿式

// 正确的父组件
...
    handleTxtChange(event,idx){// 重点:【正确姿式】 不只带了框架默认参数event,还带了自定义参数
        this.state.list[idx].text = event.target.value;
        this.setState({ // 最正常的赋值写法
            list: this.state.list
        });
        this.initDidCount();
    },
...

// 正确的子组件
...
    handleTxt(event) {
        // 用一个中转函数来存onChange时会调用的父组件的函数
        // 并加上任意的参数
        this.props.handleTxtChange(event, this.props.index);
    },
    
    render (){
        return (
            <li className="li">
                ...
                {
                    this.state.status?
                        // 重点代码开始
                        <input type="text" className="ipt"
                               defaultValue={this.props.text}
                               // 【正确姿式】调用子组件的中转函数
                               onChange={this.handleTxt}/>:
                         // 重点代码结束
                        <p className="p">{this.props.text}</p>
                }
                    ...
            </li>
        )
    }
...

若是这样写的话,是达到了和案例1同样的效果。
中转函数的效果,保证了render时的函数都是pure function
而且也防止了子组件在挂载时,render顺着this.props.function调用父组件的函数
从而避免了一系列错误。

案例3

案例3纯粹是为了演示一个增长操做,在增长一条记录后,须要清空input的内容时踩的坑

// 父组件
    addLiItem(obj) {
        this.state.list.push(obj); // 没啥好说,就是添加一个元素到list中去
        this.setState({
            list: this.state.list
        });
        this.initDidCount();
    },
// 子组件
const Add = React.createClass({
    getInitialState() {
        return {
            addValue: '',
            addStatus: false
        }
    },
    
    handleAddChange(event) {
        this.setState({
            addValue: event.target.value
        })
    },
    
    add(){
        this.props.addLiItem({
            text: this.state.addValue,
            status: false
        });
        this.setState({ //【重点部分】
            addValue: ''
        }, ()=>{
            this.refs.addIpt.value = ''; // 利用ref操做dom
        });
    },
    // 若是只是setState的时候发现完成没办法达到清空的效果
    // 这时候的【正确姿式】是去操做dom,必定要操做dom
    render() {
        return (
            <div>
               // 定义了一个ref是addIpt的input标签
                <input className="ipt" onChange={this.handleAddChange} value={this.addStatus} ref="addIpt"/>
                <button className="btn btn-save" style={{float: 'left'}} onClick={this.add}>添加</button>
            </div>
        )
    }
});
        • --

总结

为了尽量使用pure function,也为了保证挂载的时候不要出问题
在子组件须要调用父组件的this.props.function的时候
尽量使用中转函数
你懂得~~

写在后面

github地址:Close2React

我是嘉宝Appian,一个卖萌出家的算法妹纸。

相关文章
相关标签/搜索