R3-React组件从0到1

       这一章我们正式从组件开始,通过这一章,基本可以靠自己写出一些基础组件,如展示型的页面,简单表单页面等,文章中所有出现的源码都在GitHub上,共需要的童鞋下载。统一的,源码链接还是放在了文章的最后面。还有一点如果觉得文章中贴的代码太长影响了阅读可以快速扫一遍,直接阅读文字内容。跑工程源代码的时候再细看。先来看这一章的思维导图
组件基础
唉?怎么木有组件的生命周期呢。莫慌!这个是有的,生命周期比较重要,所以打算重点介绍,单独领出来放到下一章了。

1.什么是组件

       ps:(修改2018-11-19)
       个人理解:组件是完成某一特定功能的含有或逻辑或样式或结构的个体或者逻辑样式各机构三者的任意组合的独立集合体,且具有一定通用性

2.组件的创建方式

       第一种:ES6语法格式使用class进行创建,也是目前最流行的方式。通过继承React.Compoent来实现创建组件。里面必须实现render方法。我们之后文章中所有出现的组件都是这种方式创建!
       第二种:通过React.createClass实现,这个已经在react16.0.0版本抛弃了,解决方案是使用create-react-class依赖包。
       第三种:纯函数方式,无状态组件。
三种方式我写在了同一个文件中,需要显示哪一个组件则导出哪一个,代码如下(可以直接运行源码):

    import React, { Component } from 'react';
    import CreateReactClass from 'create-react-class';
    /*
    * 组件概念
    *   1.组件的创建方式
    *   2.无状态组件
    * */

    // ES6 现在主流创建组件方式
    class ComponentConcept extends Component{

    constructor(props){
        super(props);
    }

    render(){
        const { text } = this.props;
        return(
            <div>Hello Component</div>
        )
    }

    }

    // 16.0.0之前是react官方的创建组件的方式React.createClass,16.0.0之后弃用了
    // 如今想在16.0.0及以上版本使用这种方式可使用Create-React-Class包
    const ComponentConcept1 = CreateReactClass({
    getDefaultProps(){
        return {
            text:'随便命名',
        }
    },
    render(){
        const { text } = this.props;
        return (
            <div>{text}</div>
        )
    }
    })

    // 纯函数,无状态组件,只负责展示,只接收一个props和context作为参数
    function ComponentConcept2({text = "hello 纯函数"}) {

    return (
       <div>{text}</div>

    )
    }

    // export default ComponentConcept;
    // export default ComponentConcept1;
    export default ComponentConcept2;

render方法,是用来绘制页面的,即你要展示的页面内容都在这里进行编写,有以下几点需要注意
(1).必须有render函数,且必须有return,如果木有return浏览器会报错。
(2).纯函数则相当于render函数,同样必须return。
(3).return返回值必须都包裹在一个标签内。即最外层不可出现两个及以上的同级标签,return后面的小括号不写没关系,但是建议必须写,规范。

    return (
    <div></div>
    <div></div>
    )// 错误,包含了两个同级标签。其他返回这种HTML模板的函数都是一样的规则。

这种写法是对的

   return (
    <div>
      <div></div>
    </div>
    )// 正确,最外层只有一个标签。

3.无状态组件

纯函数概念:

给定相同的输入,她总是返回相同的输出;过程没有副作用;没有额外的状态依赖。

       上面第三个组件就是用纯函数生成,他没有生命周期,也就没有了状态依赖。下一章介绍生命周期,大白话说就是他创建的组件就像一个相框,相框的外形固定,你放什么相片他就展示什么相片,没有自己的更新机制。所以多用来做纯展示型组件。

4.事件系统

       最好先了解一下原生DOM事件,这里有一个传送门DOM事件
       在react开发中,事件是必须的,从最实际的开始,现在我们开发中最常用的方法绑定方式“箭头函数”

     handleBind(e,value){
        console.log('handleBind',e,value);
    }
    // 添加点击事件,仔细体会下面另种方式
     <button className='btnStyle' onClick={this.handleBind}>ErrorBind</button>
     <button className='btnStyle' onClick={(e)=>this.handleBind(e,'这里可以传值')}>ErrorBind</button>

       上面是一个点击事件,其他事件跟原生DOM事件是一样的,比如onChange,onBlur,onFocus等等,唯一区别就是react事件事件名属于驼峰命名。
       除上述绑定方式外还有另外一种方式

    <button className='btnStyle' className='btnStyle' onClick={this.handleBind.bind(this,'ddddddd')}>Bind</button>
    <button className='btnStyle' onClick={::this.handleBind}>SimpleBind</button>

       第一行使用.bind,第二行则是第一行的简写形式使用双冒号::进行绑定。

       最后说一下React合成事件的实现原理。
       事件委派,react事件并不是把事件直接绑定到真实的dom上(如果想用原生事件,则必须绑定到真实dom上,这就需要生命周期函数,前期在没有生成真实dom之前原生事件绑定是无效的),而是把事件绑定到了结构的最外层,并使用统一的事件监听器。这个事件监听器上有一个映射,保存了组件中的所有事件监听和处理函数。当触发某一个事件时则会根据映射找到对应的处理函数执行。当组件卸载或挂载时,就会在映射表中删除或添加新的事件监听映射记录。
自动绑定,在react中,每个方法的上下文都是指向该组件的实例,即自动绑定this为当前组件。
事件委派

       react事件只实现了冒泡,可以像使用原生事件一样使用stopPropagation进行阻止冒泡。这主要是因为捕获阶段很少使用,同时react中是支持使用原生事件的通过ref。下面是整理事件的几种方式,这需要多动手多体会。如果看代码太长可以直接跳读下面的内容,跑源代码的时候再看这些代码!整个工程源码在最下方有链接。

 import React, { Component } from 'react';
    import '../../style/analysisComponent/analysisStyle.scss';

    /*、
    * react 事件系统
    *   1.事件原理
    *   2.绑定方式
    *   3.原生事件
    *   */
    class ComponentAndEvent extends Component{

    constructor(props){
        super(props);
        this.state = {
            name:"事件",
        }
    }
    // 1.bind
    handleBind(e,value){
        console.log('handleBind',e,value);
    }

    // 2.arrow
    handleArrow = (e,value) => {
        console.log('handleArrow',e,value);
    }

    // 外层
    onClickFather = () => {
        console.log(1);
    }

    // 内层1
    onClickSon = () => {
        console.log(2);
    }

    // 内层2
    onClickGrandson = (e) => {
        console.log(3);
        e.stopPropagation();

    }

    componentDidMount(){
        let isCatch = false;
        // 是否是在事件分发阶段进行捕获处理,false代表冒泡阶段处理,true代表分发阶段处理
        this.refs.grandson.addEventListener('click',e => {
            console.log('grandson');
            e.stopPropagation();
        },isCatch)
        this.refs.son.addEventListener('click',e => {
            console.log('son');
        },isCatch)
        this.refs.father.addEventListener('click',e => {
            console.log('father');
        },isCatch)
    }


    render(){

        return (
            <div className='analysisStyle'>
                <button className='btnStyle' className='btnStyle' onClick={this.handleBind.bind(this,'ddddddd')}>Bind</button>
                <button className='btnStyle' onClick={::this.handleBind}>SimpleBind</button>
                <button className='btnStyle' onClick={this.handleBind}>ErrorBind</button>
                <button className='btnStyle' onClick={this.handleArrow}>=>方式1</button>
                <button className='btnStyle' onClick={(e) => this.handleArrow(e,'ddddddddd')}>=>方式2</button>
                <button className='btnStyle' onClick={this.handleArrow()}>Error=></button>
                <div className='fatherBox' onClick={this.onClickFather}>
                    <div className='sonBox' onClick={this.onClickSon}>
                        <div className='grandsonBox' onClick={this.onClickGrandson}>
                           冒泡
                        </div>
                    </div>
                </div>
                <div className='fatherBox' ref='father'>
                    <div className='sonBox' ref='son'>
                        <div className='grandsonBox' ref='grandson'>
                            冒泡
                        </div>
                    </div>
                </div>
            </div>
        )
    }

    }


    export default ComponentAndEvent;

5.props

       流向:react数据流是自上而下的单向数据流。媒介:props属性
       props是在react中编写组件时常用的属性之一,数据的流动靠的就是它。是组件之间相互联系的一种机制!我们所创建的每一组件都会有一个自带的props属性。它主要承载了来自父级的数据,比如

    // 子组件Son的render函数
    render(){
        const { name } = this.state;
        const { hello, whoUse } = this.props;
        return (
            <div>
                <div>{`姓名state: ${name}`}</div>
                <div>我是Son</div>
                <div>{whoUse}</div>
                <div>{hello}</div>
            </div>
        )
    }

       这是一个子组件(子组件名字叫Son)的render函数,我们从this.props中取出两个属性值,hello,whoUse两个。这两个值从哪里来的?注意这两个值就是调用该组件的时候在组件中传入的。开下面的调用处的代码:

  // 父组件Father的render函数
    render(){
        const { name, isShowChild, isAdd } = this.state;
        console.log('father',this.props);
        return (
            <div>
                <div>{`姓名state: ${name}`}</div>
                <div>下面是一个子组件:</div>
                <Son whoUse={'father component'} hello={'hello word'}>
                   你好我是夹在子组件标签中的内容,显示了吗?
                </Son>
            </div>
        )
    }

结果显示效果
       最后两行显示的值正式在父组件中调用Son这个组件时传入了whoUse和hello这两个属性值。如果不传则在子组件中拿不到该值。
       在仔细看发现“你好我是夹在子组件标签中的内容,显示了吗?”这句话没有显示。**实际上,除了刚才说的以属性方式放在组件中的值会被放置到props上,其标签内部的所有内容都则会被放进children中即this.props.children。**所以当子组件标签中还包含了内容时,可以在子组件的render函数中跟取出标签属性一样,取出children。如下修改:

    // 子组件Son修改后的render函数
    render(){
        const { name } = this.state;
        const { hello, whoUse, children } = this.props;
        return (
            <div>
                <div>{`姓名state: ${name}`}</div>
                <div>我是Son</div>
                {children/*显示子组件标签内部的嵌套内容*/}
                <div>{whoUse}</div>
                <div>{hello}</div>
            </div>
        )
    }

       所有从父组件中传入的值都会绑定到props这个属性上,可以是任意值,布尔,数字,对象数组等等。

6.state

       props是承载力父级组件传入的数据,组件本身的数据(需要展示带有状态的数据)应该放在哪?当然是state来解决。
     (1)初始化state,建议在constructor函数中初始化。

    constructor(props){
        super(props);
        this.state = {
            name:"Father Component",
            isShowChild: true,
            isAdd:false,
        }
    }

       也可以在class类内部创建,不需要this关键字,在其他方法中使用时使用this.state.

    class Father extends Component{

    constructor(props){
        super(props);
    }
    state = {
        name:"Father Component",
        isShowChild: true,
        isAdd:false,
    }
     ...// 省略下面代码

     (2) state是一个对象,state中任何一个数据发生变化都会触发render函数刷新页面。所以需要刷新的页面数据可以定义在state中。
     (3)更新state中的值必须使用setState方法,this.state.xxx = xxx;这种是不行的。setState方法接收两个传参数,第一个是对象,即用来更新stage中属性值得,第二个是匿名函数,是setState更新完值之后调用的(因为setState是异步方法)。
       看下面的代码(组件Son代码片段,工程源码在文章最后有GitHub链接,可下载):

    // 组件Son代码片段,运行源码看打印值。
    changeBaseNum = () => {
        this.baseNum2++;
        this.setState({result:this.baseNum2*this.baseNum1},()=>{
            console.log(2,this.state.result);// result更新之后调用,打印值是更新后的值
        });// 会触发刷新
        // this.state.result = this.baseNum2*this.baseNum1;// 错误,不会触发刷新
        console.log(1,this.state.result);// setState方法是异步的,会先打印这个,且result值为改变。
    }

7.受控组件和非受控组件

       大白话讲就是受到控制的组件和不受到控制的组件,受到谁的控制,当然是state了。受控和非受控主要指表单是否受控。
       1.如果含有表单的组件是受控的,即表单的值变化是根据state变化的,state也是跟着表单值变化的;
       2.非受控则是state可以跟着表单值变化,但是表单值并不会跟着state变化,即不受state控制。
       我们写两个含有表单的组件,一个是受控的,一个是非受控的。
       如何体现受控和非受控:
       1.表单都含有两个输入值姓名和地址;
       2.state中都各自定义好nameValue和addressValue两个属性值;
       3.并给两个input都添加onChange事件。
       4.addressValue变化时,我们都在onChange事件中设置值为“我任性,地址就是泰坦星”,观察受控和非受控页面显示情况;
       不同点:
       1.我们在render函数中受控组件nameValue和addressValue是给了value,非受控组件给了defaultValue。

具体代码如下:

  /*
    * 受控组件
    * */
    class ComponentControl extends Component{

    constructor(props){
        super(props);
        this.state = {
            componentName:'ComponentControl',
            nameValue:"",
            addressValue:"",
        }
    }

    handleNameChange = (event) => {
        this.setState({nameValue:event.target.value});// 获取输入值并更新到state的nameValue值上
        // 受控组件,注释掉上面一行则页面上输入框无法输入,因为输入框value值受state控制,state初始值是空,无变化,则页面显示一直是空。
    }

    handleAddressChange = (event) => {
        this.setState({addressValue:"我任性,地址就是泰坦星"});
    }

    render(){
        const { nameValue, addressValue } = this.state;
        return(
            <div className='analysisStyle'>
                <h2>受控组件</h2>
                <input className='inputStyle' type='text' placeholder='输入姓名' value={nameValue} onChange={this.handleNameChange}/>
                <br/>
                <input className='inputStyle' type='text' placeholder='输入地址' value={addressValue} onChange={this.handleAddressChange}/>
                <br/>
                <div>姓名:{nameValue}</div>
                <div>地址:{addressValue}</div>
                {/*调用一下非受控组件,显示在一起便于观察*/}
                <ComponentUncontrol />
            </div>
        )
    }

    }

非受控组件

 class ComponentUncontrol extends Component {

      constructor(props){
        super(props);
        this.state = {
            componentName: 'ComponentUncontrol',
            nameValue:"",
            addressValue:"",
          }
      }
  
      handleNameChange = (event) => {
        // 非受控组件同样对state的nameValue值进行更新,看input输入框显示和下面显示是不一样,因为input输入框的值不受控state控制
        this.setState({nameValue:'你好,我是谁?'},() => {
            console.log(this.state.nameValue);
        });
      }

    handleAddressChange = (event) => {
        this.setState({addressValue:"我任性,地址就是泰坦星"});
    }

    render(){
        const { nameValue, addressValue } = this.state;
        return(
            <div className='analysisStyle'>
                <h2>非受控组件</h2>
                    <input className='inputStyle' type='text' ref='nameValue' onChange={this.handleNameChange} defaultValue={nameValue} placeholder='输入姓名' />
                    <input className='inputStyle' type='text' ref='addressValue' onChange={this.handleAddressChange} defaultValue={addressValue} placeholder='输入地址' />
                <div>姓名:{nameValue}</div>
                <div>地址:{addressValue}</div>
            </div>
        )
    }
    }

然后我们对这两个值都实时在页面上输出。
输出结果

结语:通过这一章,你一已经可以写一些简单的组件了。知道什么是组件,如何创建组件,如何给事件添加事件监听,react合成事件的原理,如何刷新页面,知道什么是受控组件和非受控组件。下一章介绍【组件的生命周期】

工程源码地址,点击这里

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值