R-3.React组件从0到1

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

组件基础

唉?怎么木有组件的生命周期呢。莫慌!这个是有的,生命周期比较重要,所以打算重点介绍,单独领出来放到下一章了。

1.什么是组件

       你知道飞机✈️的结构吗?机头,机身,机翼和机尾。机头是专门给机长准备的,机身是给乘客的,机翼机尾是用来保持平衡和飞翔的,每个部分都各司其职。组件就好比这些飞机的一部分,飞机就是页面。一个页面往往需要多个组件共同完成,每个组件负责不同的功能,这些组件还能像飞机的各个部分如机翼,不光这个飞机能用,装到其他飞机(同型号)也可以。组件也是这样,具有一定的独立性。有人会问那HTML是什么?他是构成组件的一部分,组件构成不光有HTML还有逻辑js负责,样式css负责。所以我的理解是组件是完成某一特定功能的含有逻辑、样式、结构的独立集合体,且具有一定通用性

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合成事件的原理,如何刷新页面,知道什么是受控组件和非受控组件。下一章介绍【组件的生命周期】

工程源码地址,点击这里

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 作为一个合格的开发者,不要只满足于编写了可以运行的代码。而要了解代码背后的工作原理;不要只满足于自己的程序...
    六个周阅读 8,421评论 1 33
  • 本笔记基于React官方文档,当前React版本号为15.4.0。 1. 安装 1.1 尝试 开始之前可以先去co...
    Awey阅读 7,647评论 14 128
  • 原教程内容详见精益 React 学习指南,这只是我在学习过程中的一些阅读笔记,个人觉得该教程讲解深入浅出,比目前大...
    leonaxiong阅读 2,809评论 1 18
  • 说在前面 关于 react 的总结过去半年就一直碎碎念着要搞起来,各(wo)种(tai)原(lan)因(le)。心...
    陈嘻嘻啊阅读 6,846评论 7 41
  • React简介 (1)简介 React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaSc...
    鱼鱼吃猫猫阅读 1,610评论 1 6