wj'blog


  • 首页

  • 分类

  • 归档

  • 标签

  • 关于

  • 搜索

了解react中的虚拟DOM

发表于 2018-07-12 | 分类于 React

在我学习vue框架的时候,就时常听到虚拟DOM的概念,后来了解到vue2.0新增了虚拟DOM,那时我并不知道这会有什么意义,因为我只想关心怎么使用框架。时至今日,我在学习react,在网上浏览react相关文章的时候,虚拟DOM这一词汇也是被时常提及,可能是因为react框架是使用虚拟DOM的先行者吧。于是我就花了一些时间,简单地了解了一下,如果不去追究代码的实现,我也可以简单说说我的理解。

为什么需要虚拟DOM?

可能本来我应该先说说什么是虚拟DOM,但这其实没啥可说的,我们就可以把它定义为js对象,但真要说起来可能也有很多东西可以说,关于这个网上已有一大堆好文,我就不去照搬了。我就想说说为啥需要虚拟DOM?其实很多像我这样的人可能不了解虚拟DOM,但是我们却知道它可以提高性能,那么它到底是怎么提高性能呢?

设计一个框架

在这里,我不写代码,我们来假设一下,如果我们自己来实现一个react框架,那么我们会怎么来实现?好吧,我们先借鉴一下react框架,来理个思路:

  1. state(数据层),本着不直接操作DOM而是操作数据的原则,我们会有一个数据存放的地方;
  2. jsx(模板),模板最终会生成DOM结构用于展示,其实jsx语法实现的内容就相当于模板;
  3. 数据 + 模板 结合,生成DOM结构,用于显示;
  4. state数据发生变化;
  5. 数据 + 模板 结合,生成新的DOM结构,并替换掉原先的DOM结构。

按照上面这五个步骤去设计我们的框架,这完全是没有大问题的。功能是可以实现了,但缺点也是很明显的,很多时候页面只会变化一小部分,如果每次变化都要重新渲染整个DOM结构,这显然大大消耗了性能。要知道我们在使用js时都是尽量避免直接操作DOM的,所以我们需要尝试改进。

改进框架

我们知道上面步骤中需要改进的地方就是最后一步,我们不应该在数据变化时替换掉整个DOM结构,而是替换需要更改的地方,于是我们可以按如下步骤实现:

  1. state(数据层),本着不直接操作DOM而是操作数据的原则,我们会有一个数据存放的地方;
  2. jsx(模板),模板最终会生成DOM结构用于展示,其实jsx语法实现的内容就相当于模板;
  3. 数据 + 模板 结合,生成DOM结构,用于显示;
  4. state数据发生变化;
  5. 数据 + 模板 结合,生成新的DOM结构,不直接替换原先的DOM结构;
  6. 新的DOM和原始DOM作对比,找出差异部分;
  7. 找到差异的部分后,只替换需要变化的DOM结构;

上面的步骤,前四步还和以前一样,到了第五步时,我们先不直接替换整个DOM,而是在接下来比较新旧DOM,找到需要变更的地方,然后实现部分更新替换。现在,我们解决了这个整体替换DOM结构的大隐患,实现了局部更新,这听起来似乎性能嗖嗖的上去了。然鹅~聪明的你一定发现了,这里面有一个步骤似乎并不讨喜,那就是第六步,新旧DOM做对比。对比啥?是在对比DOM结构啊!这也是相当消耗性能的。搞了半天,其实性能并没有提升多少,说不定还下降了。所以,我们还需要改进!

虚拟DOM的引入

吁~终于扯到虚拟DOM上了,没错,接下来我们就加入虚拟DOM的概念来改进我们的框架。还记得我上面开始说的嘛,虚拟DOM你可以理解为就是一个js对象,不过这个对象里保存了DOM信息,我们一步一步看。

首先,前两步我们总是需要的:

  1. state(数据层);
  2. jsx(模板);

接下来,我们要结合数据和模板,但是这次我们先不直接生成DOM结构,而是生成虚拟DOM:

  1. 数据 + 模板 生成虚拟DOM (虚拟DOM是js对象,用来描述真实DOM信息),如:

    1
    2
    3
    ['div',{id:'root'},['span',{},'hello world']]

    //上面对象所描述的信息就是:<div id="root"> <span>hello world</span> </div>
  2. 用虚拟DOM结构生成真实的DOM结构,来显示,即

    1
    <div id="root"> <span>hello world</span> </div>
  3. state发生变化;

  4. 数据 + 模板 生成新的虚拟DOM,如:

    1
    2
    3
    ['div',{id:'root'},['span',{},'hi']]

    //hello world 变成 hi
  5. 比较原始虚拟DOM和新的虚拟DOM,区别就是span中内容的变更;

  6. 直接改变DOM中span中的内容。

看到这,我想你对虚拟DOM有了大概的了解了。查看上面步骤,你可能也会疑问,生成虚拟DOM这里不会消耗性能吗?其实肯定会消耗一些性能的,但相比较直接生成真实DOM结构,肯定是大大降低消耗的,而且后面的对比差异就是直接对比js对象,不涉及DOM操作,这里也是大大提升了效率的。

以上就是我对虚拟DOM的简单理解,一些地方可能说的不太对,但我的意思应该是明显的吧,嘿嘿~

最近在忙找新工作的事,其实也不忙。虽然收到几个offer,但不是自己喜欢的,哎,大家还是对应届生的能力有所怀疑啊,我表示无力,只能说我能力还是不足啊。多学学,继续加油。

redux体验

发表于 2018-06-18 | 分类于 React

在网上看过不少资料文档后,确认redux基础概念并不是想象中的难,为啥这么说呢,因为在我还没接触react时就到处听人说redux很难,所以我一直以为它很难上手。但在实际接触后,发现基本的概念并不难,甚至比当初刚接触vuex时更简单。当然了,难的我也还没接触到,现在只说最基础的。

核心概念

关于redux的概念其实我也讲不好的,为了好理解,我尽量用自己的话描述下,到底对不对,说不好~

  • store:可以看成是一个容器,保存数据的地方,要注意的是,整个应用只能有一个 Store

  • state:包含具体的数据,通过store.getState()可以拿到当前的数据,并且一个 State 对应一个 View

  • action:state的变化会导致view的更新,页面中我们是不能直接接触state的,应该是触发view才能导致state变化,可以理解为action就是view发出的通知,表示state要变化了

  • reducer:在发出action通知后,要更新state了,这个更新state的过程就是reducer,它是一个函数,接收action和state作为参数,对应更新state

我知道,刚来就看这么多概念是没用的,可以结合下面说的例子慢慢看。

感受一下redux

通常人们都把redux和react在一起说,但事实上这两个是解耦的,redux不一定要用在react应用上。为了演示方便,我们还是使用create-react-app脚手架,不过开始时,我们不结合react,而是单独看看它的使用。

将脚手架src目录下的所有文件全删除,新建一个index.js文件,写入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 引入createStore,用以创建store
import {createStore} from 'redux'

// 创建reducer
function reducer(state=0, action){
switch(action.type){
case 'ADD':
return state+1
case 'REMOVE':
return state-1
default:
return 10
}
}

// 新建store
const store = createStore(reducer)

//查看当前state
console.log(store.getState())

当你敲完这些代码,运行后,可以在控制台看到输出了10。我们来一行行看。

首先,引入了createStore,只看名字我们也知道了,是用来创建store的,它是一个函数,传入reducer作为参数即可创建出store。

再来看reducer函数,它并不是一定要以reducer作为函数名的,你可以自定义。它有两个参数,一个是state,它就是包含具体数据的,可以初始化为0,它可以是字符串,对象等多种类型。第二个参数action是view层传过来的,它包含通知信息,保存在type属性中,它的type属性值也是我们自己定义的,根据传来的信息进行不同的state更新。上面代码还没涉及到action的分发,后面再说。

接下来,就是把新建的reducer作为参数传入到createStore中,从而创建store了,store中的getState方法可以拿到当前state。输出的结果是10,因为我们并没有发送action来提示更新state,所以默认返回了10,这些看代码也很好理解。那么接下来,我们尝试发送一次更新需求:

1
2
3
4
5
// 发送ADD需求
store.dispatch({type:'ADD'})

// 再查看当前state
console.log(store.getState())

结合reducer的代码,也可以明白现在打印的就是11了,action就是通过dispatch发送的,它本质上就是一个对象,一般type属性必写,之后也可以自定义其他属性。好了,现在redux的基本概念已经走了一遍,当然实际中不可能这么简单,这里只做演示。接下来,我们结合react再来写一个实际的小例子。

简单计数器

结合react来做一个简单的计数器吧,把index.js文件改成如下:

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
import React from 'react'
import ReactDOM from 'react-dom'
import {createStore} from 'redux'
import reducer from './reducer.js'

const store = createStore(reducer)

class App extends React.Component{
remove(){
store.dispatch({
type:'REMOVE'
})
}

add(){
store.dispatch({
type:'ADD',
text:2
})
}
render(){
return (
<div>
<input type="button" value="-" onClick={this.remove.bind(this)}/>
<span>{store.getState()}</span>
<input type="button" value="+" onClick={this.add.bind(this)}/>
</div>
)
}
}

ReactDOM.render(<App/>,document.getElementById('root'))

上面代码也没有啥可说的,都看得懂,注意的是我在add函数中的action里多加了一个text属性,这个属性也是能被传到reducer里的,可以用于state的计算。

再来新建一个reducer.js文件,用来写reducer文件:

1
2
3
4
5
6
7
8
9
10
export default function reducer(state=0,action){
switch(action.type){
case 'ADD':
return state + action.text
case 'REMOVE':
return state - 1
default:
return state
}
}

现在运行浏览器,可以看到页面效果了,但点击按钮时,你会发现数字并没有增加,打开控制台,也没有报错。这是什么原因呢?

原因是在于没有监听state的变化,view是不会自动更新state的,我们需要手动监听state的变化,这时我们需要store.subscribe这个方法,Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。那么想一想,我们监听谁呢?

其实监听的方式不止一种,在这里有一种比较好理解的。每当state变化时,监听函数就会自动执行,那我们让组件重新渲染不就行了,重新渲染的组件一定是最新的state,所以我们可以将index.js文件的最后改造一下:

1
2
3
4
5
6
7
function render(){
ReactDOM.render(<App/>,document.getElementById('root'))
}

render()

store.subscribe(render)

这时,我们就可以在页面中执行数字的加减了。注意到,当点击加时,会加2个数,是因为执行加的时候,是加上了action.text,参考代码。

好了,redux的基本使用就这些了,一些更高级的用法以后再说。(其实看我懒不懒了~)

react路由

发表于 2018-06-14 | 分类于 React

react与vue一样,做SPA就要用到路由功能。我们做WEB端路由,需要用到的是react-router-dom这个库,它帮助我们实现react路由功能。另外,为了方便起见,代码演示使用react脚手架create-react-app。关于脚手架的使用可自己搜索。废话不多说,直接撸~

基本使用

首先我修改下脚手架,将/src/index.js入口文件清空,用来写我们的演示代码,将/public/index.html模板文件删掉不必要的干扰代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

切换到index.js文件,首先引入我们需要的模块:

1
2
3
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter as Router, Route, Link} from 'react-router-dom'

前两个不必多说了,而第三个就是我们要用到的路由库,在引入前别忘记下载了:

1
npm install --save react-router-dom

我们在react-router-dom中引入了三个东西,其中,Router是容器,它里面就包含了我们的路由内容,Route是定义真正路由组件的,而Link就是定义切换链接的,类似vue中的router-link,简单使用如下:

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
// 首页组件
const Home = () => (
<div>
<h1>这是首页</h1>
</div>
)

// 用户页组件
const User = () => (
<div>
<h1>这是用户页</h1>
</div>
)


ReactDOM.render(
<Router>
<div>

<ul>
<li><Link to="/home">首页</Link></li>
<li><Link to="/user">用户页</Link></li>
</ul>

<Route path="/home" component={Home}></Route>
<Route path="/user" component={User}></Route>
</div>

</Router>, document.getElementById('root')
)

代码中也可以看到,Router起到容器作用,其本身也是一个组件,需要注意的是,Router下只能有一个根元素,这里即div。先看Link,它有一to属性,很明显代表的是要链接的地址,再看Route,它的path属性表明路由的路径,与Link的to属性值要对应起来,而component属性指出当前路径所对应的界面(其本质就是对应到一个组件,当链接到当前路径后,展示组件内容)。

怎么样,react路由是不是很简单?其实刚接触时,我有点不习惯这种写法,毕竟受vue影响大了,哈哈~不过当我再写一遍时,我觉得react的路由上手更简单点,至少很直观 ,也许当初我先学react的话,会对vue的路由更不习惯。

嵌套路由

如果说当初刚学vue路由时,我可能不知道怎么定义嵌套路由,但是现在初学react路由时,我自己按着想法,却也把嵌套路由搞出来了,一切也是因为它很直观。因为react路由也是组件形式,那么我在父路由组件里,再写一个子路由不就行了嘛,形式都是一样的,如下:

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
// xiaoming
const Xiaoming = () => (
<div>
我是小明
</div>
)

// xiaohong
const Xiaohong = () => (
<div>
我是小红
</div>
)

// 用户页组件
const User = () => (
<div>
<h1>这是用户页</h1>

<Router>
<div>
<ul>
<li><Link to="/user/xiaoming">小明</Link></li>
<li><Link to="/user/xiaohong">小红</Link></li>
</ul>
<Route path="/user/xiaoming" component={Xiaoming}></Route>
<Route path="/user/xiaohong" component={Xiaohong}></Route>
</div>
</Router>

</div>
)

如上,我在/user路由组件里,再写了两个子路由,形式和父路由完全一样,这样就完成了嵌套路由的实现,同理也可继续嵌套下去。而嵌套路由在vue里的实现是配置children,很明显刚学时,我肯定是不知道是要配置children的。不过用这么久vue了,我还是很喜欢vue这个框架的,也习惯了它的设计思想,而react让我看到了不同的设计思想。我相信存在即合理,这两种模式我都喜欢,哈哈又扯多了。

动态路由

react的动态路由匹配与vue的基本类似,也是以冒号开头加参数。我们将上述例子中的user子路由修改下,我们不明确指向是xiaoming还是xiaohong,而是使用动态路由来自动识别,如下:

1
2
3
4
5
6
7
const Person = () => (
<div>
// ....
</div>
)

<Route path="/user/:name" component={Person} ></Route>

现在,我要求输入不同的name时,浏览器会显示不同的内容。其实关键就是Person组件该怎么写了,再进一步就是怎么拿到name的值了,这时候我们也会想到react应该会替我们做好这些的。其实,name参数值会被传入到Person组件里,存在props里,而函数定义的组件中,props是以参数形式传进去的。我们可以打印props就会知道name值存在props.match.params中:

1
2
3
4
5
const Person = (props) => (
<div>
{props.match.params.name}
</div>
)

知道了怎么获得动态参数,我们就把这个例子完善好:

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
const Person = (props) => {
if(props.match.params.name === 'xiaoming'){
var a = '我是小明'
}else{
var a = '我是小红'
}
return (
<div>{a}</div>
)
}


// 用户页组件
const User = () => (
<div>
<h1>这是用户页</h1>

<Router>
<div>
<ul>
<li><Link to="/user/xiaoming">小明</Link></li>
<li><Link to="/user/xiaohong">小红</Link></li>
</ul>
<Route path="/user/:name" component={Person} ></Route>
</div>
</Router>
</div>
)

上述代码的功能与之前写的一样,只是用动态路由来实现的。当然我这种写法是很 low的,实际开发中应该不会这么写,我只是把过程讲出来,方便理解。

react路由就先讲这么多了,我也实在讲不出花来~

react组件设置样式

发表于 2018-06-03 | 分类于 React

本篇主要介绍给组件设置样式时,准确说是给JSX里的标签设置样式时要注意的地方。

class

当要给组件添加class时,不能直接写成class,应该写成className:

1
2
3
4
5
class Demo extends React.Component{
render(){
return <div className='div1'></div>
}
}

这主要因为class是JavaScript的保留字。而且JSX 的特性更接近 JavaScript 而不是 HTML , 所以 React DOM 使用 camelCase 小驼峰命名来定义属性的名称,而不是使用 HTML 的属性名称。

style

除了通过添加class来设置样式外,我们也可以使用style内联样式。只不过这跟以往写的有些不同,你需要将样式内容用双花括号包起来,如下:

1
2
3
4
5
class Demo extends React.Component{
render(){
return <div style={{color:'white',fontSize:'16px'}}></div>
}
}

这跟vue里的mustache语法很像,也是双大括号,但是这里应该理解为一层花括号里包含一个对象,react将这个对象渲染为内联样式。另外你需要注意的是,遇到font-size这种的要写成小驼峰命名形式,不然会出错。

react组件通信

发表于 2018-06-01 | 分类于 React

说到组件,那就不能不说组件通信。个人觉得相对于vue,react的通信比较容易理解。

父组件向子组件通信

react是单向数据流,数据主要从父节点传递到子节点(通过props)。这种传递方式也是比较简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 子组件
class Son extends React.Component{
render(){
return <div>{this.props.title}</div>
}
}

// 父组件
class Parent extends React.Component{
render(){
return <Son title="我是父组件传给子组件的值"/>
}
}

ReactDOM.render(<Parent/>,app)

子组件向父组件通信

因为react是单向数据流,数据只能有父组件传递到子组件,所以要想从子组件更新父组件,需要一个取巧的方法,那就是利用回调函数,简单概述就是:父组件通过props传递一个回调函数到子组件中,这个回调函数可以更新父组件,子组件就是通过触发这个回调函数,从而使父组件得到更新

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
// 子组件
class Son extends React.Component{
render(){
return <button onClick={this.props.handleChange}>点我可以改变父组件的状态值</button>
}
}

// 父组件
class Parent extends React.Component{
constructor(){
super()
this.state={
val:0
}
}

change(){
let newVal = this.state.val + 1
this.setState({
val:newVal
})
}
render(){
return <div>
<Son handleChange={this.change.bind(this)}/>
<p>{this.state.val}</p>
</div>

}
}

这次,父组件通过props传一个回调函数给子组件,子组件通过触发这个回调函数来更新父组件的状态。

除了这两种最普遍的通信外,还有其他情况的通信,比如兄弟组件等,这里先不说了,准备等我遇到了再说。

react事件

发表于 2018-05-30 | 分类于 React

事件处理

React 元素的事件处理和 DOM元素很相似,主要有两点不同:

  • React事件绑定属性的命名采用驼峰式写法,而不是小写
  • 如果采用 JSX 的语法你需要传入一个函数作为事件处理函数,而不是一个字符串(DOM元素的写法)
1
2
3
4
5
6
//DOM元素
<button onclick="test()"></button>


//react
<button onClick={test}></button>

写个小例子:

1
2
3
4
5
6
7
8
9
10
11
12
class Demo extends React.Component{

show(){
alert('hello')
}

render(){
return <button onClick={this.show}>点击</button>
}
}

ReactDOM.render(<Demo/>,app)

上例中,当点击按钮时,调用 show函数,注意要用this调用,this指向组件本身。关于this,来单独说下。

this

我们来改变一下上面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Demo extends React.Component{
constructor(){
super()
this.state = {
val:0
}
}

show(){
this.setState({
val:this.state.val+1
})
}

render(){
return <div>
<button onClick={this.show}>点击</button>
<p>{this.state.val}</p>
</div>

}
}

此例中,我们为组件添加状态,并显示在页面中,当点击按钮时,状态的数值加1。不过运行的结果可能不会如意,报一个错误:

1
...Cannot read property 'setState' of undefined

提示setState未定义,为了验证,我们在show函数里打印一下this,结果显示时 undefined。到这里,其实我开始也是比较迷惑的,函数里的this指向undefined还是第一次见。

在查看文档后才知道,原来在react里,类的方法默认是不会绑定 this 的,也就是说我们必须手动绑定this。绑定到哪?我们的一切操作都是基于这个组件的,所以应该绑定到当前类上。怎么绑定?直接在JSX 回调函数上绑定,如下:

1
<button onClick={this.show.bind(this)}>点击</button>

这里的this还是指向当前类的,只是类的方法不会默认绑定this而已。

默认行为和事件冒泡

说到事件,就不得不说默认行为和事件冒泡,因这两点在react里也有些不同。

取消默认行为

在react里,必须明确的使用 preventDefault来取消默认行为,像return false是没用的。

写个例子,阻止a标签的跳转:

1
2
3
4
5
6
7
8
handleClick(e){
e.preventDefault()
console.log('hello')
}

render(){
return <a href="https://www.baidu.com" onClick={this.handleClick}>点击</a>
}

事件冒泡

在react里,使用e.stopPropagation()阻止事件冒泡可能会没效,因为这里的冒泡分为三种情况,情况不同方法也不同,在这里我找到一篇文章,说的很详细,我就不多啰嗦了,其实是偷懒,哈哈~
传送门

react组件

发表于 2018-05-29 | 分类于 React

学习react应该就是学习它的组件化开发了,看看它和vue的组件相比有何不同?

组件

react组件有两种,分别是函数定义和类定义。

函数定义组件

1
2
3
function Demo(){
return <h1>hello world</h1>
}

上面代码就创建了一个有效的组件,它是用函数返回了一个React元素,这种类型的组件为函数定义组件,我们可以正常使用它:

1
2
3
4
ReactDOM.render(
<Demo/>,
app
)

记住ReactDOM.render()的第一个参数是要渲染的内容,组件不就是要展示的内容吗?只是组件的使用应写成标签形式,这点应该很熟悉,跟vue的一样。

这里有一个特别需要注意的地方,那就是组件名称必须以大写字母开头,不然无法渲染。

看到上面的代码,不知道你会不会有一种想法,一种下意识的想法,看代码:

1
2
3
4
5
function Demo(){
return <h1>hello world</h1>
}

ReactDOM.render(Demo(),app)

看到了吗?我不把Demo写成标签形式,而是直接调用了,结果依然正确渲染了。细想下,这本该如此啊,因为函数返回的是JSX啊,这与直接写JSX并没有本事区别。此时,你要是把函数名写成小写也不会有影响了,因为它已经不是一个组件了:

1
2
3
4
5
function demo(){
return <h1>hello world</h1>
}

ReactDOM.render(demo(),app)

类定义组件

相对于函数定义组件,类定义组件才是最常用的,因为它允许我们更多的扩展。

类定义组件,其实就是es6中定义类的方法,只是react加入了一些东西:

1
2
3
4
5
6
7
class Demo extends React.Component{
render(){
return <h1>hello world</h1>
}
}

ReactDOM.render(<Demo/>,app)

上面就是类定义组件,熟悉es6的话,应该比较熟悉这种形式,在定义一个组件时,我们必须让它继承React.Component这个类,这样才能使用react的一些功能。

关于类定义组件,要讲得东西其实还是有几个的,在这之前,我们要了解两个和组件息息相关的概念:属性和状态。

组件的属性和状态

组件有属性和状态,分别表示为props和state,它们的区别:

  • 属性是由父组件传递给子组件的
  • 状态是子组件内部维护的数据,当状态发生变化的同时,组件也会进行更新。当状态发生转换时会触发不同的钩子函数,从而让开发者有机会做出相应

属性(props)

属性是写在父组件上的,可以传递给子组件:

1
2
3
4
function Demo(props){
return <h1>{props.msg}</h1>
}
ReactDOM.render(<Demo msg='hello world'/>,app)

上面是函数定义组件的属性传递写法,Demo函数接收一个单一的“props”对象,这里包含了所有的父组件的属性,我们多写几个属性,打印props看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Demo(props){
console.log(props)
return <h1>{props.msg}</h1>
}
ReactDOM.render(<Demo msg='hello world' name='wj' age='18'/>,app)


//props打印结果
{
age:"18",
msg:"hello world",
name:"wj"
}

需要注意的是,在react里要输出数据到模板里,用一个花括号即可,在vue里是两个花括号。

属性不仅仅可以是字符串,还可以是js的表达式:

1
2
const num = 5
ReactDOM.render(<Demo msg={num>3?'大于3':'小于3'} name='wj' age='18'/>,app)

这里就不多说了,react相对于vue,做了更少的限制,大胆去尝试吧,很多你觉得可以的,在react确实可以做到。

再来看看在类定义组件里使用props:

1
2
3
4
5
6
7
class Demo extends React.Component{
render(){
return <h1>{this.props.msg}</h1>
}
}

ReactDOM.render(<Demo msg='hello world' />,app)

这里使用props的唯一区别就是要使用this来调用,其他跟函数定义组件一样。

props有一个限制,那就是它的只读性,无论是使用函数或是类来声明一个组件,它决不能修改它自己的props。如果需要改变props值来更新视图显然是不行的。这就需要state状态。

状态(state)

状态适用于类定义的组件,状态是私有的,完全受控于当前组件。我们需要使用状态,则需要定义类组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Demo extends React.Component{
constructor(){
super()
this.state={
msg:'hello world'
}
} // 注意这里不要写逗号,详见es6之class
render(){
return <div>{this.state.msg}</div>
}
}

ReactDOM.render(<Demo/>,app)

上面代码添加了状态,在这里你可能需要知道一些es6的class知识,推荐阮一峰老师的es6教程。我们知道Demo类式继承React.Component这个类的,算子类,它必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。

在this对象上添加状态state,这个状态是属于Demo私有的,使用this.state.xxx的方式调用。

状态受控于当前组件,是可以改变的,这里我们来改变下状态,注意这里会使用到事件,以后会详细介绍react事件:

1
2
3
4
5
6
7
8
9
10
change(){
this.state.msg = '改变了'
}

render(){
return <div>
<input type='button' value='改变' onClick={this.change.bind(this)} /><br/>
<p>{this.state.msg}</p>
</div>
}

上面代码中,当我们点击按钮时,state数据会改变,同时视图也会更新。但是当你运行后,会发现视图并不会更新。原因在哪里?

其实问题就出在了this.state上,我们不能直接使用this.state来变更状态,这样不会重新渲染组件,我们需要使用setState()方法来更新状态:

1
2
3
4
5
6
7
change(){
//this.state.msg = '改变了'

this.setState({
msg:'改变了'
})
}

这样,当状态更新后,组件才会重新渲染。

总结

我也不知道怎么总结了,很多细节我也没说到,这算是自己的一个回顾吧。与vue相比,react似乎限制地更少,这也意味着我们可以更灵活地组织我们的代码。学习过程中应该大胆尝试,你想到的,react作者们可能也想到了。另外在vue里,组件不是必须的,更多的关注点是在数据层上,但在react里,组件是不可缺少的,一切开发都是基于组件的(至少现在我是这么觉得),这也反映了两大框架的不同思想吧,不管如何,多学学总不会差。

react初体验

发表于 2018-05-28 | 分类于 React

前言

在我的日常开发中,我用的只有vue,对于大名鼎鼎的react和angular,我一点都不了解。想当初开始学框架时,纠结许久,无非是react更具挑战性,学会了再学vue更容易,而vue简单易上手且功能也不输react。当然这些都是从度娘听说的。

使用vue许久,感觉还是很爽的,虽然开始时有点困难,毕竟那时es6也不熟悉。我不止一次庆幸我学的是vue,不仅是因为它易学易用,关键是它的生态也越来越好,国内很多新东西出来,都是先基于vue的,比如现在很火的mpvue小程序框架,就是基于vue的,这使得我学会一个vue就可以容易地做很多事。

之所以有庆幸学的是vue的心态,可能也是因为不了解其他两大框架,先入为主地觉得vue才是王道。我不知道其他两个框架是否很难入手,是否更加强大,但是vue已经能够解决我的日常开发了,我本来也不想去接触react和angular的,可能觉得舍易求难是浪费时间。不过另一方面,我又很好奇,它们真的那么难吗?和vue比到底有什么区别?思来想去,我觉得还是直接试水比较好,不就是一个框架吗?想学就学咯!于是,我选择了react,因为angula似乎真的难点!

hello world

当初学vue时,我只要引入一个文件vue.js就好了,学react,我竟然要引入三个文件,天啊,我第一想法就是这个好复杂啊。

  • react.js:实现React核心逻辑,且于具体的渲染引擎无关,从而可以跨平台公用。
  • react-dom.js:包含了具体的DOM渲染更新逻辑,以及服务端渲染的逻辑,与浏览器相关。
  • browser.js:支持JSX语法必须。

在这里,我用的react是15.6.2版本,听说稳定点,browser.js是5.8.38版本,莫名不敢用新的,哈哈~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<body>
<div id="app"></div>

<script src="./common/react.js"></script>
<script src="./common/react-dom.js"></script>
<script src="./common/browser.js"></script>

<script type="text/babel">
ReactDOM.render(
<h1>hello world</h1>,
document.getElementById('app')
)
</script>
</body>

注意:script标签的type属性应写成text/babel,不然识别不了react语法

上面代码是截取body部分,此时已经可以在浏览器里输出hello world了。虽然这段代码看起来很简单,但是我开始写时,我是恐慌的,因为ReactDOM.render()看起来很复杂的样子,为啥不是vue那样,直接两个花括号写到html里呢,那样多简单。

确实很容易受vue影响,对于这种方法有点接受不了,但这其实并不复杂。ReactDOM.render()的第一参数很明显就是我们要输出到浏览器的内容,那么第二个参数其实是要输出到的位置。

当我们明白两个参数的意义时,可能觉得懂了,其实不然。这里面可是有一个核心的东西,那就是JSX。在我还没接触JSX,总是听别人说JSX时,我觉得它可能是一个很复杂的语法。但当我接触后才知道,这货很“纯”,是一个奇怪的变量:

1
var ele = <h1>hello world</h1>

这种看起来可能有些奇怪的标签语法既不是字符串也不是 HTML,它就是JSX。那么不禁想问,它到底是什么数据类型?

1
2
3
console.log(typeof ele)

//object

显然,它是一个对象,这么看的话,JavaScript是在内部给它处理了。事实也确实如此:

1
2
3
4
5
6
7
8
9
10
ReactDOM.render(
React.createElement('h1',null,'hello world'),
document.getElementById('app')
)


ReactDOM.render(
<h1>hello world</h1>,
document.getElementById('app')
)

上面两种方法结果完全相同。React.createElement()的第一个参数是标签,第二个参数是标签属性,第三个参数是标签内容。很显然这样写比较繁琐,且不直观。所以我们应该直接使用JSX语法,这也是官方建议的。

其实我们直接写JSX语法,Babel 转译器也会把 JSX 转换成为 React.createElement() 方法的调用,而这个方法会返回一个对象,类似这样:

1
2
3
4
5
6
{
type: 'h1',
props: {
children: 'Hello, world'
}
}

这样的对象被称为 “React 元素”。它代表所有你在屏幕上看到的东西。React 通过读取这些对象来构建 DOM 并保持数据内容一致。

关于JSX还有两点需要注意:

  1. 当有多个标签时,在最外层需加上一个包裹标签,例如:

    1
    const ele = <div><span></span><p></p></div>
  2. JSX允许自由分段

    1
    2
    3
    4
    const ele = <div>
    <span></span>
    <p></p>
    </div>

总结

在react里使用ReactDOM.render()来向页面渲染内容,它有两个参数,第一个参数是渲染内容,第二个参数是渲染位置。

JSX是JavaScript的一种扩展语法,让一个变量直接赋值为标签。它与React.createElement()方法调用的结果是一样的,返回一个对象,叫做“React元素”。react推荐使用JSX语法。

啰嗦一大堆,其实是表达自己的心情,初见react让我觉得复杂不好用,但是越接触越发现它是有它独特的魅力的。不管哪一个框架,我们都没资格去评定好不好,只有适不适合自己。

koa初体验

发表于 2018-05-23 | 分类于 JavaScript

出于对koa的好奇,于是想要了解下。

koa 是由 Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架。

如果说koa是下一代web开发框架,那么上一代毫无疑问就是express了,其实用哪个都无所谓啦,只要自己用的顺手就好。反正我哪个都用不到,至少目前是的,哈哈。

开始

新建一个文件夹koaDemo,其内执行:

1
2
3
npm init -y

npm install koa

我们讨论的是koa2。而koa默认安装的就是2的版本,所以无需指定为koa2。

用koa来输出“hello world”,新建一个文件server.js,输入以下内容:

1
2
3
4
5
6
7
8
const Koa = require('koa')
const app = new Koa()

app.use(async ctx => {
ctx.body = 'hello world'
})

app.listen(3000)

node server.js执行文件,此时打开浏览器,访问:http://localhost:3000/,熟悉的hello world就显示出来啦。

可以看到,koa真的很精简,短短几行就能启动一个服务,虽然这些express都能办到,但是koa肯定也是有它的独到之处的。其中之一就是使用 async+await 处理异步,上面代码中已用到了async,接下来就介绍下async和await。

async与await

koa的一大优点就是No Callback,很多人使用koa也是因为这个。没有回调得力于async和await的使用,关于它们的概念我也讲不好,网上有很多文档可以参考,你只需要知道它可以解决回调地狱的问题,使异步操作更优雅。具体怎么个优雅法,我们来慢慢看。

回调函数处理异步

在最初的时候,我们使用回调函数来处理异步,来看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
function test() {
setTimeout(()=>{
console.log('执行了')
}, 2000)
}

test()
console.log('结束')

// 执行结果
结束
执行了

上面代码的执行结果显然不符合预期的,所以我们需要借助回调函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function test(fn) {
setTimeout(()=>{
console.log('执行了')
fn()
}, 2000)
}

test(()=>{
console.log('结束')
})

// 执行结果
执行了
结束

显然,这才是我们想要的结果,回调虽然看起来很简单,但是当业务逻辑多起来时,容易形成回调地狱,比如:

1
2
3
4
5
6
7
ajax(()=>{
ajax(()=>{
ajax(()=>{
// ...
})
})
})

三层,不能再多了!三层都快受不了了,更别说更复杂的了,当维护这样的代码时,是真的难受。所以后来es6给了一个好的解决方法,那就是promise。

promise

关于promise诞生的前前后后我就不介绍了,因为我也不太熟悉啊,不过它的使用方法我们还是要掌握的,推荐阮一峰老师的es6教程,传送门:promise

来写一个promise的例子,假设三个异步操作,依次执行:

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
function delay(val){
return new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve(val)
},2000)
})
}

delay('第一步执行')
.then((val)=>{
console.log(val)
return delay('第二步执行')
})
.then((val)=>{
console.log(val)
return delay('第三步执行')
})
.then((val)=>{
console.log(val)
})

// 结果
第一步执行
第二步执行
第三步执行

promise的使用,解决了一层层回调嵌套的窘境,这很大程度上解决了回调地狱的问题,清晰的代码结构也容易维护。

promise很好用,但是代码量还是有点的,如果可以,何不试试async+await呢?

async+await

虽然我也想把这两个概念讲的很明白,但是我不能啊,网上有大佬讲的很清楚,在这我就简单说下。

你需要知道的是,async用来定义一个异步函数,它总会返回异步对象,即promise,而await必须在async内部使用,用来处理异步结果。

说不如做,就拿上面的例子来试下async的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function delay(val){
return new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve(val)
},2000)
})
}

async function start(){
let result1 = await delay('第一次执行')
console.log(result1)

let result2 = await delay('第二次执行')
console.log(result2)

let result3 = await delay('第三次执行')
console.log(result3)
}
start()

// 结果
第一步执行
第二步执行
第三步执行

如上代码,同步的方式写异步,再也看不到回调了,是不是很爽。在koa中,重点使用async+await的方式,所以不懂得童鞋还得加把劲啊。即使不用koa,但这么炫酷的方法你确定不试试?

总结

写完后才发现,我不是介绍koa的,反而介绍了async,虽然没讲得明白。麻雀虽小五脏俱全,koa的更多使用,下次再说咯。

express中实现文件上传

发表于 2018-03-19 | 分类于 JavaScript

介绍

之所以要单独说下文件上传,是因为body-parser中间件不支持文件类型获取。我们平时都是用这个中间件去获取post数据,但是如果post过来的是一个文件,那它就不管用了。所以,我们需要新的中间件,那就是multer。

multer

Multer 是一个 node.js 中间件,用于处理 multipart/form-data 类型的表单数据,它主要用于上传文件。

关于它的用法,可以参考文档:multer

注意:Multer 不会处理任何非 multipart/form-data 类型的表单数据。

什么是 multipart/form-data类型?

表单标签form上有一个enctype属性,它有三种值:

  • application/x-www-form-urlencoded:在发送前编码所有字符(默认)
  • multipart/form-data:不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。
  • text/plain:空格转换为 “+” 加号,但不对特殊字符编码。

上传一个文件

现在就来实现一个文件上传。在这之前,确保你已经安装了express和multer。

app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const express = require('express')
const app = express()

//引入multer
const multer = require('multer')

//注册一个对象,dest里放的是上传的文件存储的位置,可以在当前目录下,建立一个static目录,上传的文件都放在这里
const upload = multer({dest: './static/'})

//使用中间件,没有挂载路径,应用的每个请求都会执行该中间件。any表示接受一切,具体参考文档。
app.use(upload.any())

//在req.files中获取文件数据
app.post('/',function(req, res){

console.log(req.files)

res.send('上传成功')
})


app.listen(3000)

现在准备一个表单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<form action="http://localhost:3000/" method="post" enctype="multipart/form-data">
选择文件:
<input type="file" name="file1"><br>
<input type="submit" value="上传">
</form>
</body>
</html>

注意:在form里的enctype属性上必须填”multipart/form-data”。

现在执行app.js文件

1
node app.js

在浏览器打开表单html文件,并上传一个文件,我上传了一张图片,然后在命令行里可以看到打印的数据,比如我的是:

1
2
3
4
5
6
7
8
[ { fieldname: 'file1',
originalname: 'html5.jpg',
encoding: '7bit',
mimetype: 'image/jpeg',
destination: './static/',
filename: '2047b375bfaa68f984af6ac19e8df455',
path: 'static\\2047b375bfaa68f984af6ac19e8df455',
size: 9029 } ]

可以看到req.files是一个数组,因为可能不止传一个文件,每个文件的信息也很清晰就不多介绍了。

需要注意的是filename这个属性,它就是上传后的文件名,可以到static目录里去看,它就是这样一个文件名且没有后缀,我们现在打不开它的,如果加上后缀,就可以打开了。我的图片是jpg格式,我在文件名后面加上.jpg就可以正常打开了。

手动添加后缀名太麻烦了,既然用了node,那我们何不进一步处理,直接拿到有后缀名的文件呢?

优化

其实思路很简单,看打印出的文件信息,我们可以从originalname里拿到文件后缀名,又可以在path里拿到文件路径。好了,要做的无非就是拿到后缀名再重命名嘛。使用node中的path和fs模块,很容易实现。

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
const express = require('express')
const app = express()
const multer = require('multer')

//引入 path 和 fs
const path = require('path')
const fs = require('fs')

const upload = multer({dest: './static/'})

app.use(upload.any())

app.post('/',function(req, res){
console.log(req.files)

//拿到后缀名
var extname = path.extname(req.files[0].originalname);

//拼接新的文件路径,文件加上后缀名
var newPath = req.files[0].path + extname;

//重命名
fs.rename(req.files[0].path, newPath, function(err){
if(err){
res.send('上传失败')
}else{
res.send('上传成功')
}
})
})


app.listen(3000)
123…6
wungjyan

wungjyan

56 日志
9 分类
24 标签
RSS
练习册
© 2020 wungjyan
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.2
本站访客数 人次 本站总访问量 次