react 入门与进阶教程 一

前端学习对于我们来说越来越不友好,特别是随着这几年的发展,入门门槛越来越高,连进阶道路都变成了一场马拉松。在学习过程中,我们面临很多选择,vue与react便是一个两难的选择。

两者都是非常优秀的框架,而且我不能非常主观的说谁好谁不好。但是从我们初学者的角度来说,其实我们没有必要去考虑谁的性能更好,谁的实现更优雅,谁更适合什么场景等各种因素,唯一一个需要考虑的标准就是,学习谁,能够让我们更快的掌握它。因为无论他们两个你掌握了谁,都能够让你在找工作时更有底气。这就足够了。

悟空源码有一套腾讯课堂的react前端课程讲的很不错,需要的可以下载看下 一线大厂React实践宝典视频教程  

因此,我这篇文章的目的,则是希望从与官方文档不同的角度,来试图让react学习变得更加容易,如果你想要学习react,不妨花点时间读下去。

为什么对于新人来说,官方文档不能帮助你掌握得更好

对于vue的学习,很多朋友有一个大的误解,认为vue官方出了中文文档,所以掌握起来会更加容易。然而事实上并非如此。

官方文档可能告诉了你vue/react的基础知识有哪些,可是这些知识怎么用,官方文档并没有告诉我们。而且vue官方文档为了降低学习门槛(绕开了vue-cli),在讲述知识的时候,不少地方其实与实际开发是有差距的,这个差距会导致你看完了官方文档,仍然不知道如何使用vue做一些事情。

当然,这样的问题,react官方文档也存在。虽然对于经验丰富的大神来说,这并不是问题,但是对于新人来说,这样的差距往往会使得大家有一种似懂非懂的感觉。

这也是我为什么要从和官方文档不一样的角度来入手的原因。

react中文文档:http://www.react-cn.com/docs/getting-started.html

学前准备

在准备学习本文的react知识之前,希望你已经拥有了ES6的知识与知道了create-react-app的安装与使用,我们的学习将会建立在这基础之上,如果你暂时还没有接触过他们,不用担心,可以回过头去阅读我的前两篇文章。不用花太多时间就可以初步掌握。

ES6常用知识合集
详解create-react-app 与 ES6 modules

你可以暂时不用对react有什么基础的了解,我们可以从0开始,当然,如果你看过官方文档或者从其他地方学习过相关知识就更好了。

react官方中文文档

开始啦,万能的Hello World程序

首先,假设你已经在电脑上安装好了create-react-app并知道如何使用,那么我就开始在你电脑上存放开发项目的目录(本文中假设为develop)里开始创建一个名为first-react的react项目。操作顺序如下:

 

 

1

2

3

4

5

6

7

8

9

10

11

// 在develop目录创建first-react项目

> create-react-app first-react

 

// 进入新创建的项目

> cd first-react

 

// 安装项目依赖包

> npm install

 

// 安装完毕之后启动项目

> npm start

启动之后,效果分别如下图所示:

命令行工具中

浏览器中

自动生成的项目是一个简单的react demo。这个时候项目中会有三个文件夹,我们来分别了解一下这三个文件夹的作用。

  • node_modules
    项目依赖包存放位置。当我们运行npm install安装package.json中的依赖包时,该文件夹会自动创建,所有的依赖包会安装到该文件夹里。

  • public
    主要的作用是html入口文件的存放。当然我们也可以存放其他公用的静态资源,如图片,css等。其中的index.html就是我们项目的入口html文件。

  • src
    组件的存放目录。在create-react-app创建的项目中,每一个单独的文件都可以被看成一个单独的模块,单独的image,单独的css,单独js等,而所有的组件都存放于src目录中,其中index.js则是js的入口文件。虽然我们并没有在index.html中使用script标签引入他,但是他的作用就和此一样。

我们在最初学习开发一个页面的时候,就已经知道一个页面会有一个html文件,比如index.html,然后分别在html文件中,通过script与link标签引入js与css。但是在构建工具中,我们只需要按照一定的规则来组织文件即可,整合的工作构建工具会自动帮助我们完成,这也是构建工具给前端开发带来的便利之处,也因为如此,前端的模块化开发才成为了可能。

我们还是和上一篇文章中说的一样,先清空src目录里所有的其他文件,仅仅只留下空的入口文件index.js,并在index.js写入如下的代码:

 

 

 

 

 

JavaScript

 

1

2

3

4

5

6

7

// src/index.js

import React from 'react';

import { render } from 'react-dom';

 

const root = document.querySelector('#root');

 

render(<div>Hello World!</div>, root);

 

 

保存之后,结果如下:

Hello World!

如何你能轻松看懂这四行代码,那么说明你离掌握react已经不远了。至少你已经掌握了ES6的相关知识。我来解释一下这些代码的作用。

  • import React from 'react';
    在我们通过npm install指令安装依赖包的时候,就已经安装好了react,因此我们可以直接import。这句话的作用就在于,能够让构建工具在当前模块中识别jsx。而jsx,是一种类似于html标签的模板语言,我们只需要懂得html标签,就不必花费额外的精力去了解jsx,因为我们可以直接理解为它就是html标签,但是在此基础上,扩展了更多的能力。例如这里,程序能够识别

     

    Hello World!

    ,正是这句话的作用。

  • import { render } from 'react-dom';
    这是利用ES6的解析结构的语法,仅仅引入了react-domrender方法。render方法的作用,就是将react组件,渲染进DOM结构中,它的第一个参数就是react 组件,第二个参数则是一个DOM元素对象。

  • const root = document.querySelector('#root');
    这句话就很简答了,如果你理解不了,那么说明你的基础还不足以支撑你学习react, – -。

  • render(<div>Hello World!</div>, root);

    这是最核心的一步,通过render方法,将写好的react组件渲染进DOM元素对象。而这里的root,则是在index.html中写好的一个元素。这里的div,可以理解为一个最简单的react组件。

OK,理解了这些,那么我们就可以开始学习react最核心的内容组件了。

react组件

曾经,创建react组件有三种方式,但是既然都决定在ES6的基础上来学习react了,那么我也就只介绍其中的两种方式了。反正另外一种方式也已经被官方废弃。

当一个组件,并没有额外的逻辑处理,仅仅只是用于数据的展示时,我们推荐使用函数式的方式来创建一个无状态组件。

我们结合简单的例子来理解。在项目的src目录里创建一个叫做helloWorld.jsx的文件。在该文件中,我们将创建一个正式的react组件,代码如下:

 

 

 

 

 

JavaScript

 

1

2

3

4

5

6

7

8

9

10

// src/helloWorld.jsx

import React from 'react';

 

const HelloWorld = () => {

    return (

        <div>Hello World!</div>

    )

}

 

export default HelloWorld;

 

 

并在index.js中引入该组件。修改index.js代码如下:

 

 

 

 

 

JavaScript

 

1

2

3

4

5

6

7

8

9

10

// src/index.js

import React from 'react';

import { render } from 'react-dom';

 

// 引入HelloWorld组件

import HelloWorld from './helloWorld';

 

const root = document.querySelector('#root');

 

render(<HelloWorld />, root);

保存后运行,我们发现结果一样。

helloWorld.jsx中,我们仍然引入了react,是因为所有会涉及到jsx模板的组件,我们都要引入它,这样构建工具才会识别得到。

组件里只是简单的创建了一个HelloWorld函数,并返回了一段html(jsx模板)。并在最后将HelloWorld函数作为对外的接口暴露出来export default HelloWorld

接下来我们通过一点一点扩展HelloWorld组件能力的方式,来学习组件相关的基础知识。

向组件内部传递参数

向组件内部传递参数的方式很简单,这就和在html标签上添加一个属性一样。

例如我们希望向HelloWorld组件内传递一个name属性。那么只需要我们在使用该组件的时候,添加一个属性即可。

 

 

1

2

<HelloWorld name="Tom" />

<HelloWorld name="Jake" />

如果我们希望组件最终渲染的结果是诸如:Tom say: Hello, world!其中的名字可以在传入时自定义。那么我们在组件中应该如何接收传递进来的参数呢?

我们修改HelloWorld.jsx如下:

 

 

 

 

 

JavaScript

 

1

2

3

4

5

6

7

8

9

10

11

// src/helloWorld.jsx

import React from 'react';

 

const HelloWorld = (props) => {

    console.log(props);

    return (

        <div>{ props.name } say: Hello World!</div>

    )

}

 

export default HelloWorld;

 

 

并在index.js中修改render方法的使用,向组件中传入一个name属性

 

 

1

2

// src/index.js

render(, root);

结果如下:

输出props参数

HelloWorld组件中,我使用了一个叫做props的参数。而通过打印出来props可以得知,props正是一个组件在使用时,所有传递进来属性组合而成的一个对象。大家也可以在学习时多传入几个额外的参数,他们都会出现在props对象里。

而在jsx模板中,通过

{ props.name } say: Hello World!

这样的方式来将变量传入进来。这是jsx模板语言支持的一种语法,大家记住能用即可。

大家要记住,使用这种方式创建的无状态组件,会比另外一种方式性能更高,因此如果你的组件仅仅只是用于简单的数据展示,没有额外的逻辑处理,就要优先选择这种方式。

那么我们继续升级HelloWorld组件的能力。现在我希望有一个点击事件,当我们点击该组件时,会在Console工具中打印出传入的name值。这就涉及到了另外一种组件的创建,也就是当我们的组件开始有逻辑处理,之前的那种方式胜任不了时索要采取的一种形式。

修改helloWorld.jsx文件如下:

 

 

 

 

 

 

JavaScript

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

// helloWorld.jsx

import React, { Component } from 'react';

 

class HelloWorld extends Component {

    clickHander = () => {

        console.log(this.props);

        console.log(this.props.name);

    }

 

    render () {

        return (

            <div onClick={this.clickHander}>{ this.props.name } say: Hello World!</div>

        )

    }

}

 

export default HelloWorld;

 

 

如果,你同时熟知第一种react组件的创建方式,与ES6语法的话,相信上面的代码,并不会对你造成多少困扰。

没错,这种方式创建的组件,正是通过继承react的Component对象而来。所以创建的方式也是利用ES6的class语法来生成。也正因为如此,其中的很多实用方式,也就跟class的使用一样了。

上面的render方法,则是Component中,专门提供的用来处理jsx模板的方法。

与第一种方式不同的是,我们接收传入进来的参数,使用的是this.props,第一种方式将props放置于函数参数中,而这种方式则是将props挂载与实例对象上,因此会有所不同。

而我们想要给一个组件添加点击事件,方式也与html标签中几乎一致

 

react事件相关的知识大家可以当做一个进阶课程去研究,这里就暂时不多说,详情可以参考官方文档 https://facebook.github.io/react/docs/events.html

好了,现在大家初步认识了react的第二种组件的创建方式,那么我们继续搞事情,现在我想要的效果,是传入两个名字,name1=Tom, name2='Jason',我希望第一次点击时,log出Tom,第二次log出Jason,第三次Tom…

这个时候,我们就需要引入react组件非常核心的知识状态state

修改helloWorld.jsx代码如下:

 

 

 

 

 

 

JavaScript

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

// helloWorld.jsx

import React, { Component } from 'react';

 

class HelloWorld extends Component {

    state = {

        switch: 0,

        name: this.props.name1

    }

 

    clickHander = () => {

        const { name1, name2 } = this.props;

 

        if (this.state.switch === 0) {

            console.log(name1);

            this.setState({

                switch: 1,

                name: name2

            })

        } else {

            console.log(name2);

            this.setState({

                switch: 0,

                name: name1

            })

        }

    }

 

    render () {

        return (

            <div onClick={this.clickHander}>{ this.state.name } say: Hello World!</div>

        )

    }

}

 

export default HelloWorld;

 

 

 

先来说说state相关的基础知识。首先了解ES6 class语法的同学都应该知道,当我们通过这种方式来写的时候,其实是将state写入了构造函数之中。

 

 

1

2

3

4

state = {}

 

// 等同于ES5构造函数中的

this.state = {}

 

因此深入掌握class语法对于学习react组件的帮助非常巨大,我们需要清楚的知道什么样的写法会放入对象的什么位置,是构造函数中,还是原型中等。这也是为什么开篇我会强调一定要先对我的前两篇文章所介绍的知识有一定了解才行。

因此,在对象中,我们可以通过this.state的方式来访问state中所存储的属性。同时,react还提供了如下的方式来修改state的值

 

 

1

2

3

this.setState({

    name: 'newName'

})

setState接收一个对象,它的运行结果类似于执行一次assign方法。会修改传入的属性,而其他的属性则保持不变。

react赋予state的特性,则是当state被修改时,会引起组件的一次重新渲染。即render方法会重新执行一次。也正是由于这个特性,因此当我们想要改变界面上的元素内容时,常常只需要改变state中的值就行了。这也是为什么结合render方法,我们可以不再需要jquery的原因所在。

setState也有一个非常重要的特性,那就是,该方法是异步的。它并不会立即执行,而会在下一轮事件循环中执行。

说到这里,基础薄弱的同学就开始头晕了,这就是为什么我在前面的文章都反复强调基础知识的重要性,基础扎实,很多东西稍微一提,你就知道是怎么回事,不扎实,到处都是让你头晕的点,不知道的没关系,读我这篇文章 http://www.jianshu.com/p/12b9f73c5a4f。

相信不理解这个点的同学肯定会遇到很多坑,所以千万要记住了。

 

 

1

2

3

4

5

6

7

// 假设state.name的初始值为Tom,我们改变它的值

this.setState({

    name: 'Jason'

})

 

// 然后立即查看它的值

console.log(this.state.name) // 仍然为Tom,不会立即改变

 

refs

我们知道,react组件其实是虚拟DOM,因此通常我们需要通过特殊的方式才能拿到真正的DOM元素。大概说一说虚拟DOM是个什么形式存在的,它其实就是通过js对象的方式将DOM元素相关的都存储其实,比如一个div元素可能会是这样:

 

 

1

2

3

4

5

6

7

8

9

// 当然可能命名会是其他的,大概表达一个意思,不深究哈

{

    nodeName: 'div',

    className: 'hello-world',

    style: {},

    parentNodes: 'root',

    childrenNodes: []

    ...

}

而我们想要拿到真实的DOM元素,react中提供了一种叫做ref的属性来实现这个目的。

修改helloWorld.jsx如下:

 

 

 

 

 

JavaScript

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

import React, { Component } from 'react';

 

class HelloWorld extends Component {

    clickHander = () => {

        console.log(this.refs)

    }

 

    render () {

        return (

            <div className="container" onClick={this.clickHander}>

                <div ref="hello" className="hello">Hello</div>

                <div ref="world" className="world">World</div>

            </div>

        )

    }

}

 

export default HelloWorld;

 

 

为了区分ES6语法中的class关键字,当我们在jsx中给元素添加class时,需要使用className来代替

我们在jsx中,可以给元素添加ref属性,而这些拥有ref属性的元素,会统一放在组件对象的refs中,因此,当我们想要访问对应的真实DOM时,则通过this.refs来访问即可。

refs

当然,ref的值不仅仅可以为一个名字,同时还可以为一个回调函数,这个函数会在render渲染时执行,也就是说,每当render函数执行一次,ref的回调函数也会执行一次。

修改helloWorld.jsx如下,感受一下ref回调的知识点

 

 

 

 

 

JavaScript

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

// src/helloWorld.jsx

import React, { Component } from 'react';

 

class HelloWorld extends Component {

    clickHander = () => {

        console.log(this.refs)

    }

 

    refCallback = (elem) => {

        console.log(elem);

    }

 

    render () {

        return (

            <div className="container" onClick={this.clickHander}>

                <div ref="hello" className="hello">Hello</div>

                <div ref={this.refCallback} className="world">World</div>

            </div>

        )

    }

}

 

export default HelloWorld;

 

 

大概介绍一下我暂时能想到的ref使用的一个场景。例如我们要实现元素拖拽的时候,或者写一个slider组件。我们可能会非常频繁的改动元素的位置。这个时候,如果我们仍然通过react组件的state来存储元素的位置,那么就会导致react组件过于频繁的渲染,这就会引发一个严重的性能问题。所以这个时候我们不得不获取到真实DOM,并通过常规的方式来做。

同样的道理也适用于vue中,我们要尽量避免将可能会变动频率非常高的属性存放于vue组件的data中。

组件生命周期

所谓组件的生命周期,指的就是一个组件,从创建到销毁的这样一个过程。

而react为组件的生命周期提供了很多的钩子函数。很多地方也为生命周期画了很清晰明了的图帮助大家理解。但是我在初学的时候其实并没有看懂,还是在我懂得了生命周期之后,才看懂的那些图。所以呢,这里我也就不去找图了。我们这样理解。

通俗来说,react为一个组件,划分了如下的时刻。

  • 组件第一次渲染完成的前后时刻,
    componentWillMount 渲染完成之前
    componentDidMount 渲染完成之后

所谓的渲染完成,即组件已经被渲染成为真实DOM并插入到了html之中。

  • 组件属性(我们前面提到的props与state)更新的前后时刻
    componentWillReceiveProps接收到一个新的props时,在重新render之前调用
    shouldComponentUpdate 接收到一个新的state或者props时,在重新render之前调用
    componentWillUpdate 接收到一个新的state或者props时,在重新render之前调用
    componentDidUpdate 组件完成更新之后调用

  • 组件取消挂载之前(取消之后就没必要提供钩子函数了)
    componentWillUnmount

在学习之初你不用记住这些函数的具体名字,你只需要记住这三个大的时刻即可,第一次渲染完成前后,更新前后,取消之前。当你要使用时,再查具体对应的名字叫什么即可。

而且根据我的经验,初学之时,其实也不知道这些钩子函数会有什么用,会在什么时候用,这需要我们在实践中慢慢掌握,所以也不用着急。当我们上手写了几个稍微复杂的例子,自然会知道如何去使用他们。

所以这里我只详细介绍一下,我们最常用的一个生命周期构造函数,组件第一次渲染完成之后调用的componentDidMount

既然是组件第一次渲染完成之后才会调用,也就是说,该函数在react组件的生命周期中,只会调用一次。而渲染完成,则表示组件已经被渲染成为真实DOM插入了html中。所以这时候就可以通过ref获取真实元素。记住它的特点,这会帮助我们正确的使用它。

修改helloWorld.jsx如下:

 

 

 

 

 

JavaScript

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

// src/helloWorld.jsx

import React, { Component } from 'react';

 

class HelloWorld extends Component {

    clickHander = () => {

        console.log(this.refs)

    }

 

    // 这时已经可以获取到真实DOM,而componentWillMount则不行

    componentDidMount (props) {

        console.log(this.refs)

    }

 

    render () {

        return (

            <div className="container" onClick={this.clickHander}>

                <div ref="hello" className="hello">Hello</div>

                <div ref="world" className="world">World</div>

            </div>

        )

    }

}

 

export default HelloWorld;

我们在实际开发中,常常需要通过ajax获取数据,而数据请求的这个行为,则最适合放在componentDidMount中来执行。

通常会在首次渲染改变组件状态(state)的行为,或者称之为有副作用的行为,都建议放在componentDidMount中来执行。主要是因为state的改动会引发组件的重新渲染。




本文链接:/study/develop/18324.html

版权声明:本文来源于互联网,如有侵权,请联系下方邮箱,一个工作日删除!