前端学习对于咱们来讲愈来愈不友好,特别是随着这几年的发展,入门门槛愈来愈高,连进阶道路都变成了一场马拉松。在学习过程当中,咱们面临不少选择,vue与react即是一个两难的选择。css
二者都是很是优秀的框架,并且我不能很是主观的说谁好谁很差。可是从咱们初学者的角度来讲,其实咱们没有必要去考虑谁的性能更好,谁的实现更优雅,谁更适合什么场景等各类因素,惟一一个须要考虑的标准就是,学习谁,可以让咱们更快的掌握它。由于不管他们两个你掌握了谁,都可以让你在找工做时更有底气。这就足够了。html
所以,我这篇文章的目的,则是但愿从与官方文档不一样的角度,来试图让react学习变得更加容易,若是你想要学习react,不妨花点时间读下去。前端
对于vue的学习,不少朋友有一个大的误解,认为vue官方出了中文文档,因此掌握起来会更加容易。然而事实上并不是如此。vue
官方文档可能告诉了你vue/react的基础知识有哪些,但是这些知识怎么用,官方文档并无告诉咱们。并且vue官方文档为了下降学习门槛(绕开了vue-cli),在讲述知识的时候,很多地方其实与实际开发是有差距的,这个差距会致使你看完了官方文档,仍然不知道如何使用vue作一些事情。node
固然,这样的问题,react官方文档也存在。虽然对于经验丰富的大神来讲,这并非问题,可是对于新人来讲,这样的差距每每会使得你们有一种似懂非懂的感受。react
这也是我为何要从和官方文档不同的角度来入手的缘由。jquery
react中文文档:http://www.react-cn.com/docs/getting-started.htmlios
在准备学习本文的react知识以前,但愿你已经拥有了ES6的知识与知道了create-react-app
的安装与使用,咱们的学习将会创建在这基础之上,若是你暂时尚未接触过他们,不用担忧,能够回过头去阅读个人前两篇文章。不用花太多时间就能够初步掌握。git
ES6经常使用知识合集
详解create-react-app 与 ES6 modulesgithub
你能够暂时不用对react有什么基础的了解,咱们能够从0开始,固然,若是你看过官方文档或者从其余地方学习过相关知识就更好了。
首先,假设你已经在电脑上安装好了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。这个时候项目中会有三个文件夹,咱们来分别了解一下这三个文件夹的做用。
咱们在最初学习开发一个页面的时候,就已经知道一个页面会有一个html文件,好比index.html,而后分别在html文件中,经过script与link标签引入js与css。可是在构建工具中,咱们只须要按照必定的规则来组织文件便可,整合的工做构建工具会自动帮助咱们完成,这也是构建工具给前端开发带来的便利之处,也由于如此,前端的模块化开发才成为了可能。
咱们仍是和上一篇文章中说的同样,先清空src目录里全部的其余文件,仅仅只留下空的入口文件index.js
,并在index.js
写入以下的代码:
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);
|
保存以后,结果以下:
如何你能轻松看懂这四行代码,那么说明你离掌握react已经不远了。至少你已经掌握了ES6的相关知识。我来解释一下这些代码的做用。
import React from 'react';
npm install
指令安装依赖包的时候,就已经安装好了react,所以咱们能够直接import。这句话的做用就在于,可以让构建工具在当前模块中识别jsx。而jsx,是一种相似于html标签的模板语言,咱们只须要懂得html标签,就没必要花费额外的精力去了解jsx,由于咱们能够直接理解为它就是html标签,可是在此基础上,扩展了更多的能力。例如这里,程序可以识别
,正是这句话的做用。
import { render } from 'react-dom';
react-dom
的render
方法。render方法的做用,就是将react组件,渲染进DOM结构中,它的第一个参数就是react 组件,第二个参数则是一个DOM元素对象。const root = document.querySelector('#root');
render(<div>Hello World!</div>, root);
render
方法,将写好的react组件渲染进DOM元素对象。而这里的
root
,则是在
index.html
中写好的一个元素。这里的
div
,能够理解为一个最简单的react组件。
OK,理解了这些,那么咱们就能够开始学习react最核心的内容组件
了。
曾经,建立react组件有三种方式,可是既然都决定在ES6的基础上来学习react了,那么我也就只介绍其中的两种方式了。反正另一种方式也已经被官方废弃。
当一个组件,并无额外的逻辑处理,仅仅只是用于数据的展现时,咱们推荐使用函数式的方式来建立一个无状态组件。
咱们结合简单的例子来理解。在项目的src目录里建立一个叫作helloWorld.jsx
的文件。在该文件中,咱们将建立一个正式的react组件,代码以下:
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
代码以下:
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
以下:
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);
|
结果以下:
在HelloWorld
组件中,我使用了一个叫作props的参数。而经过打印出来props能够得知,props正是一个组件在使用时,全部传递进来属性组合而成的一个对象。你们也能够在学习时多传入几个额外的参数,他们都会出如今props对象里。
而在jsx模板中,经过
这样的方式来将变量传入进来。这是jsx模板语言支持的一种语法,你们记住能用便可。
你们要记住,使用这种方式建立的无状态组件,会比另一种方式性能更高,所以若是你的组件仅仅只是用于简单的数据展现,没有额外的逻辑处理,就要优先选择这种方式。
那么咱们继续升级HelloWorld组件的能力。如今我但愿有一个点击事件,当咱们点击该组件时,会在Console工具中打印出传入的name值。这就涉及到了另一种组件的建立,也就是当咱们的组件开始有逻辑处理,以前的那种方式胜任不了时索要采起的一种形式。
修改helloWorld.jsx
文件以下:
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
代码以下:
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,不会当即改变
|
咱们知道,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
以下:
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
来访问便可。
固然,ref的值不只仅能够为一个名字,同时还能够为一个回调函数,这个函数会在render渲染时执行,也就是说,每当render函数执行一次,ref的回调函数也会执行一次。
修改helloWorld.jsx
以下,感觉一下ref回调的知识点
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之中。
componentWillReceiveProps
接收到一个新的props时,在从新render以前调用shouldComponentUpdate
接收到一个新的state或者props时,在从新render以前调用componentWillUpdate
接收到一个新的state或者props时,在从新render以前调用componentDidUpdate
组件完成更新以后调用componentWillUnmount
在学习之初你不用记住这些函数的具体名字,你只须要记住这三个大的时刻便可,第一次渲染完成先后,更新先后,取消以前。当你要使用时,再查具体对应的名字叫什么便可。
并且根据个人经验,初学之时,其实也不知道这些钩子函数会有什么用,会在何时用,这须要咱们在实践中慢慢掌握,因此也不用着急。当咱们上手写了几个稍微复杂的例子,天然会知道如何去使用他们。
因此这里我只详细介绍一下,咱们最经常使用的一个生命周期构造函数,组件第一次渲染完成以后调用的componentDidMount
。
既然是组件第一次渲染完成以后才会调用,也就是说,该函数在react组件的生命周期中,只会调用一次。而渲染完成,则表示组件已经被渲染成为真实DOM插入了html中。因此这时候就能够经过ref获取真实元素。记住它的特色,这会帮助咱们正确的使用它。
修改helloWorld.jsx
以下:
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的改动会引起组件的从新渲染。
做为react学习中的一个很是重要的点,组件之间的交互仍是须要咱们认真掌握的。这个时候hello world就知足不了咱们学习的欲望了,因此咱们能够先把它给删掉。
那么组件之间的交互,大概能够分为以下两种:
固然可能有的人会问,2个不相干的组件之间如何交互?若是,你的代码里,出现了两个不相干的组件还要交互,那说明你的组件划分确定是有问题的。这就是典型的给本身挖坑找事儿。即便确实有,那也是经过react-redux把他们变成子组件对吧。可是,一般状况下,不到万不得已,并不建议使用react-redux,除非你的项目确实很是庞大了,须要管理的状态很是多了,已经不得不使用,必定要记住,react-redux这类状态管理器是最后的选择。
咱们来想一想一个简单常见的场景:页面里有一个submit提交按钮,当咱们点击提交后,按钮前出现一个loading图,并变为不可点击状态,片刻以后,接口请求成功,飘出一个弹窗,告诉你,提交成功。你们能够想想,这种场景,借助react组件应该如何作?
首先能够很简单的想到,将按钮与弹窗分别划分为两个不一样的组件:
。而后建立一个父组件来管理这两个子组件
。
那么在父组件中,咱们须要考虑什么因素?Button的loading图是否展现,弹窗是否展现对吧。
OK,根据这些思考,咱们开始来实现这个简单的场景。
首先建立一个Button
组件。在src目录下建立一个叫作Button.jsx
的文件,代码以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// src/Button.jsx
import React from 'react';
const Button = (props) => {
const { children, loading, submit } = props
return (
<button onClick={submit} disabled={ loading ? 'disabled' : null }>
{ loading && <i className="loading"></i> }
{ children }
</button>
)
}
export default Button;
|
注意,当你引入了一个新建立的文件时,可能须要从新启动服务才会找获得新的组件
因为这里的Button组件仅仅是简单的展现,并没有额外的逻辑须要处理,所以咱们使用无状态的组件。在这个组件里,出现了一个新的知识点:children
1
2
3
4
5
6
7
8
9
|
// 假如咱们这样使用Button组件时
<Button>确认</Button>
// 那么标签中间的确认二字就会放入props的children属性中
// 无状态组件中
props.children = '确认'
// 有状态组件中
this.props.children = '确认'
|
固然,children还能够是更多的元素,这和咱们熟知的DOM元素的children保持一致。
还有一个须要注意的知识点,则是在jsx模板中,咱们可使用JavaScript表达式来执行简单的逻辑处理
咱们能够列举一些常见的表达式:
1
2
3
4
5
6
7
|
<div>{ message }</div>
<Button disabled={ loading ? 'disabled' : null }></Button>
{ dialog && <Dialog /> }
{ pending ? <Aaaa /> : <Bbbb /> }
|
若是对于JavaScript表达式了解不够多的朋友,建议深刻学习一下相关的知识。
理解了这些知识以后,相信对于上面的Button组件所涉及到的东西也就可以很是清楚知道是怎么回事了。接下来,咱们须要建立一个弹窗组件,Dialog.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// src/Dialog.jsx
import React, { Component } from 'react';
const Dialog = (props) => {
const { message, close } = props;
return (
<div className="dialog-backdrop">
<div className="dialog-container">
<div className="dialog-header">提示</div>
<div className="dialog-body">{ message }</div>
<div className="dialog-footer">
<button className="btn" onClick={ close }>肯定</button>
</div>
</div>
</div>
)
}
export default Dialog;
|
这个组件没有太多特别的东西,惟一须要关注的一点是,咱们也能够经过props传递一个函数给子组件。例如这里的close方法。该方法在父组件中定义,可是却在子组件Dialog中执行,他的做用是关闭弹窗。
咱们很容易知道父组件想要修改子组件,只须要经过改变传入的props属性便可。那么子组件想要修改父组件的状态呢?正是父组件经过向子组件传递一个函数的方式来改变。
该函数在父组件中定义,在子组件中执行。而函数的执行内容,则是修改父组件的状态。这就是close的原理,咱们来看看父组件中是如何处理这些逻辑的。
建立一个父组件App.jsx
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
// src/App.jsx
import React, { Component } from 'react';
import Button from './Button';
import Dialog from './Dialog';
import './style.css';
class App extends Component {
state = {
loading: false,
dialog: false,
message: 'xxx'
}
submit = () => {
this.setState({
loading: true
})
// 模拟数据请求的过程,假设数据请求会经历1s获得结果
setTimeout(() => {
// 经过随机数的方式模拟可能出现的成功与失败两种结果
const res = Math.random(1);
if (res > 0.5) {
this.setState({
dialog: true,
message: '提交成功!'
})
} else {
this.setState({
dialog: true,
message: '提交失败!'
})
}
this.setState({ loading: false })
}, 1000)
}
close = () => {
this.setState({
dialog: false
})
}
render () {
const { loading, dialog, message } = this.state;
return (
<div className="app-wrap">
<Button loading={ loading } submit={ this.submit }>提交</Button>
{ dialog && <Dialog message={ message } close={ this.close } /> }
</div>
)
}
}
export default App;
|
App组件的state中,loading用于判断Button按钮是否显示loading图标,dialog用于判断是否须要显示弹窗,message则是表示弹窗的提示内容。
咱们自定义的钩子函数submit
和close
则分别是与子组件Button与Dialog交互的一个桥梁。前面咱们说过了,想要在子组件中改变父级的状态,就须要经过在父组件中建立钩子函数,并传递给子组件执行的方式来完成。
在App.jsx中咱们还看到代码中引入了一个css文件。这是构建工具帮助咱们整合的方式,咱们能够直接将css文件当作一个单独的模块引入进来。咱们还能够经过一样的方式引入图片等资源。
style.css也是在src目录下建立的。
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
// src/style.scss
button {
background: none;
border: none;
outline: none;
width: 100px;
height: 30px;
border: 1px solid orange;
border-radius: 4px;
font-size: 16px;
display: block;
margin: 20px auto;
}
.loading {
display: inline-block;
width: 10px;
height: 10px;
border: 2px solid #ccc;
border-radius: 10px;
margin-right: 10px;
border-bottom: transparent;
border-top: transparent;
animation-name: loading;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
.dialog-backdrop {
background: rgba(0, 0, 0, 0.2);
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.dialog-container {
width: 300px;
background: #FFFFFF;
border-radius: 4px;
position: absolute;
top: 20%;
left: 50%;
transform: translate(-50%, -50%);
padding: 10px;
}
.dialog-header {
height: 20px;
text-align: center;
line-height: 20px;
}
.dialog-body {
line-height: 1.6;
text-align: center;
margin-top: 20px;
}
.dialog-footer {
margin-top: 20px;
}
.dialog-footer button {
margin: 0 auto;
border: none;
background: orange;
color: #fff;
}
@keyframes loading {
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}
|
最后修改index.js,便可将程序运行起来。
1
2
3
4
5
6
7
8
|
// src/index.js
import React from 'react';
import { render } from 'react-dom';
import App from './App';
const root = document.querySelector('#root');
render(, root);
|
那么总结一下组件之间的交互。
父组件改变子组件,经过改变传入的props属性值便可。
而子组件改变父组件的状态,则须要在父组件中建立钩子函数,而后让钩子函数经过props传递给子组件,并在子组件中执行。
那么子组件与子组件之间的交互方式,也就是经过影响共同的父组件来进行交互的。正如咱们这个例子中的点击按钮,出现弹窗同样。这就是react组件之间交互的核心。
在学习异步组件以前,可能还须要你们去折腾一下如何禁用浏览器的跨域限制。禁用跨域限制可让咱们使用更多的公共api进行学习,可是不少人并不知道还能够这样玩。总之一句话,知道了如何禁用浏览器的跨域限制,会让你的学习速度提高不少,不少项目你就能够动手本身尝试了。
我这里只能提供在mac环境下如何禁用chrome浏览器的跨域限制。在命令行工具中输入如下指令启动chrome便可。
1
|
> open -a "Google Chrome" --args --disable-web-security --user-data-dir
|
在safari浏览器中则更加简单。
windows环境下如何作须要你们本身去研究。
OK,禁用跨域限制之后,咱们就能够自如的请求别人的接口。这个时候再来学习异步组件就能轻松不少。
异步组件并非那么复杂,因为接口请求会经历一点时间,所以在组件第一次渲染的时候,并不能直接将咱们想要的数据渲染完成,那么就得再接口请求成功以后,从新渲染一次组件。上面的知识已经告诉你们,经过使用this.setState
修改state的值能够达到从新渲染的目的。
因此咱们一般的作法就是在接口请求成功以后,使用this.setState
。
为了下降学习难度,咱们暂时先使用jquery中提供的方法来请求数据。
目前比较经常使用的是axios
首先在咱们的项目中,安装jquery库。咱们一般都会使用这样的方式来安装新的组件和库。
1
|
> npm install jquery
|
而后在src目录下建立一个News.jsx,借助知乎日报的api,咱们来尝试完成一个简单的异步组件。
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
// src/News.jsx
import React, { Component } from 'react';
import $ from 'jquery';
class News extends Component {
state = {
stories: [],
topStories: []
}
componentDidMount() {
$.get('http://news-at.zhihu.com/api/4/news/latest').then(resp => {
console.log(resp);
this.setState({
stories: resp.stories,
topStories: resp.top_stories
})
})
}
render() {
const { stories, topStories } = this.state;
// 观察每一次render数据的变化
console.log(this.state);
return (
<div className="latest-news">
<section className="part1">
<div className="title">最热</div>
<div className="container">
{
topStories.map((item, i) => (
<div className="item-box" key={i}>
<img src={ item.image } alt=""/>
<div className="sub-title">{ item.title }</div>
</div>
))
}
</div>
</section>
<section className="part2">
<div className="title">热门</div>
<div className="container">
{
stories.map((item, i) => (
<div className="item-box" key={i}>
<img src={ item.images[0] } alt=""/>
<div className="sub-title">{ item.title }</div>
</div>
))
}
</div>
</section>
</div>
)
}
}
export default News;
|
在style.css
中简单补上相关的css样式
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
|
// src/style.css
.latest-news {
width: 780px;
margin: 20px auto;
}
.latest-news section {
margin-bottom: 20px;
}
.latest-news .title {
height: 40px;
line-height: 40px;
font-size: 16px;
padding: 0 10px;
}
.latest-news .container {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
.latest-news .item-box {
width: 30%;
overflow: hidden;
margin-bottom: 20px;
}
.latest-news .item-box img {
width: 100%;
height: 200px;
}
.latest-news .item-box .sub-title {
font-size: 12px;
line-height: 1.6;
margin-top: 10px;
}
|
并在App.jsx中引入使用便可。
1
2
3
4
5
|
// src/App.jsx
import News from './News';
// 将下面这一句放于render函数的jsx模板中便可
|
这个组件除了获取数据,没有额外的逻辑处理,但仍然有几个须要很是注意的地方。
一、 若非特殊状况,尽可能保证数据请求的操做在componentDidMount
中完成。
二、 react中的列表渲染一般经过调用数组的原生方法map方法来完成,具体使用方式可参考上例。
三、为了确保性能,被渲染的每一列都须要给他配置一个惟一的标识,正入上栗中的key={i}
。咱们来假想一个场景,若是咱们在数组的最前面新增一条数据,若是没有惟一的标识,那么全部的数据都会被从新渲染,一旦数据量过大,这会形成严重的性能消耗。惟一标识会告诉react,这些数据已经存在了,你只须要渲染新增的那一条就能够了。
四、若是你想要深刻了解该组件的具体变化,你能够在render方法中,经过console.log(this.state)
的方式,观察在整个过程当中,组件渲染了多少次,已经每一次this.state
中的具体值是什么,是如何变化的。
不少人写文章喜欢把问题复杂化,所以当我学习高阶组件的时候,查阅到的不少文章都给人一种高阶组件高深莫测的感受。可是事实上却未必。咱们经常有一些口头俗语,好比说“包一层”就是能够用来简单解释高阶组件的。在普通组件外面包一层逻辑,就是高阶组件。
在进一步学习高阶组件以前,咱们来回顾一下new与构造函数之间的关系。在前面我有文章提到过为何构造函数中this在运行时会指向new出来的实例,不知道还有没有人记得。我将那段代码复制过来。
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
36
37
38
|
// 先一本正经的建立一个构造函数,其实该函数与普通函数并没有区别
var Person = function(name, age) {
this.name = name;
this.age = age;
this.getName = function() {
return this.name;
}
}
// 将构造函数以参数形式传入
function New(func) {
// 声明一个中间对象,该对象为最终返回的实例
var res = {};
if (func.prototype !== null) {
// 将实例的原型指向构造函数的原型
res.__proto__ = func.prototype;
}
// ret为构造函数执行的结果,这里经过apply,将构造函数内部的this指向修改成指向res,即为实例对象
var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
// 当咱们在构造函数中明确指定了返回对象时,那么new的执行结果就是该返回对象
if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
return ret;
}
// 若是没有明确指定返回对象,则默认返回res,这个res就是实例对象
return res;
}
// 经过new声明建立实例,这里的p1,实际接收的正是new中返回的res
var p1 = New(Person, 'tom', 20);
console.log(p1.getName());
// 固然,这里也能够判断出实例的类型了
console.log(p1 instanceof Person); // true
|
在上面的例子中,首先咱们定义了一个本质上与普通函数没区别的构造函数,而后将该构造函数做为参数传入New函数中。我在New函数中进行了一些的逻辑处理,让New函数的返回值为一个实例,正由于New的内部逻辑,让构造函数中的this可以指向返回的实例。这个例子就是一个“包一层”的案例。
再来看一个简单的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import React, { Component } from 'react';
class Div extends Component {
componentDidMount() {
console.log('这是新增的能力');
}
render () {
return (
<div>{ this.props.children }</div>
)
}
}
export default Div;
|
在上面的例子中,咱们把html的DIV标签做为基础元件。对他新增了一个输出一条提示信息的能力。而新的Div组件,就能够理解为div标签的高阶组件。因此到这里但愿你们已经理解了包一层的具体含义。
react组件的高阶组件,就是在基础react组件外面包一层,给该基础组件赋予新的能力。
OK,咱们来试试定义第一个高阶组件,该高阶组件的第一个能力,就是向基础组件中传入一个props参数。
在例子中,传入的参数可能没有任何实际意义,可是在实际开发中,咱们能够传入很是有必要的参数来简化咱们的代码和逻辑。
先来定义一个拥有上述能力的高阶组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// src/Addsss.jsx
import React from 'react';
// 定义一个接受一个react组件做为参数的函数
function Addsss(Container) {
// 该函数返回一个新的组件,咱们能够在该组件中进行新能力的附加
return class Asss extends React.Component {
componentDidMount() {}
render() {
return (
{ this.props.children }
)
}
}
}
export default Addsss;
|
尽管这个高阶组价足够简单,可是他已经呈现了高阶组件的定义方式。如今咱们在一个基础组件中来使用该高阶组件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// src/basic.jsx
import React, { Component } from 'react';
import Addsss from './Addsss';
class Basic extends Component {
componentDidMount() {
console.log(this.props.name);
}
render() {
return (
<div className={this.props.name}>{ this.props.children }</div>
)
}
}
export default Addsss(Basic);
|
咱们看到其实在基础组件中,对外抛出的接口是Addsss(Basic)
,这是高阶组件里定义的函数运行的结果。也就是说,其实基础组件中返回的是高阶组件中定义的Asss中间组件。这和new的思路几乎彻底一致。
固然,想要理解,并熟练使用高阶组件并非一件容易的事情,你们初学时也不用非要彻底掌握他。当你对react慢慢熟练以后,你能够尝试使用高阶组件让本身的代码更加灵活与简练。这正是向函数式编程思惟转变的一个过程。
在进步学习的过程当中,你会发现不管是路由组件react-router,或者react-redux都会使用高阶组件来实现一些功能。只要你遇到他们的时候,你能明白,哦,原来是这么回事儿就好了。
react提供了react-router组件来帮助咱们实现路由功能。
可是react-router是一个不太好讲的知识点。由于因为react-router 4进行了颠覆性的更新,致使了react-router 3与react-router 4的使用方式大不同。也正是因为变化太大,因此不少项目仍然正在使用react-router3,而且没有过渡到react-router4的打算。
所以这里我就很少讲,提供一些参考学习资料。
未完待续
因为时间关系,暂时就只能写到这里了。
原本还写了一个比较完整的例子也在这篇文章里逐步分析如何实现的,可是时间确实不够。因此若是以为看了上面的知识还想进一步学习的话,能够先去https://github.com/yangbo5207/advance15 看看这个完整例子的样子。
另外我曾经写了一篇如何快速掌握一门前端框架,但愿你们能够参考参考。
按照个人计划,只要理解了上面我所提到的知识,并把我准备的这个完整例子理解了。那么你的react掌握程度也算是小有所成了。至少应届毕业生找工做能提到这些思惟方式应该会颇有帮助。