您的位置:首页 > Web前端 > React

学习React阶段性总结

2017-05-11 11:25 323 查看
自己学习React也有一段时间了,应该做一个阶段性总结了,看了一下慕课网上的一些课程的介绍(自己舍不得花钱学-。-),然后自己截取了里面知识点介绍,对比一下自己的学习内容,试着去解释一下下面的知识点。

----------------------------------------------------------------------------------------------------------------------------------------------

React.js组件基础
虚拟 DOM 概念
React 组件
React 多组件嵌套
JSX 内置表达式
生命周期

React.js属性与事件
State 属性、Props 属性
事件和数据的双向绑定(组件通信)
可复用组件
组件的 Refs
独立组件间共享 Mixins(ES6下是用高级组件替代了Mixins)

React.js样式

内联样式及其表达式
CSS 模块化
JSX 样式与 CSS 的互转
Ant Design 样式框架

React.js Router
Router 概念
Router 参数传递

(下面就按照我的一个思路和目前学习的进度进行总结)

----------------------------------------------------------------------------------------------------------------------------------------------

一.虚拟DOM的概念:

首先我用几个问题来引出这个话题

如果我们想创建一个DOM节点我们有哪些办法?

1.直接在HTML里面写标签<div>,<p>,<span>.....等。

2.利用document.creatEelement,document.createTextNode创建我们的节点,然后appendchild进行相关嵌套在js中实现。

如果我们想给创建有属性的DOM节点呢?

1.在HTML中我们直接就是<div class="xxx" id="xxx"> 这样创建的属性只能是这个标签能拥有的,不能进行扩展(HTML5中可以进行扩展自定义属性data-xxx这样的形式)。

2.在js中我们就直接setAttribute( ) 方法添加指定的属性(我们可以自定义属性)。

(根据上面两个问题会引发一个思考)
如果我们有多个标签能不能批量去创建呢(不想一个个再去调用document.creatEelement,document.createTextNode,appendchild)?



如果我们想操作多个标签,有没有什么简单的方法,非要一个个先获取到标签然后一个个再去调用API去操作,去修改,如果遇到了标签之间的互换位置,删除等操作,我们是不是又要重复去创建,或者去思考一个算法来优化呢?


=================================================================================

首先我们可以利用面向对象的思想去解决,配合”工厂模式“批量建造我们的“DOM树”

创建一个形如下面这样的一个DOM节点

<ul class="list">
<li class="item">1</li>
<li class="item">2</li>
</ul>


我们可以把它想象成一个对象

var ul={
tagName:"ul",
props:{
class:"list"
},
childern:[
{
tagName:"li",
props:{
class:"item"
},
children:["1"]
},

{
tagName:"li",
props:{
class:"item"
},
children:["2"]
},
]
}


创建我们的构造器函数进行批量处理:

function Element(tagName,props,children){
this.tagName=tagName;
this.props=props;
this.children=children;
}

var ul=new Element("ul",{class:"list"},[
new Element("li" , { class: " item" }, [ 1 ] ),
new Element("li" , { class: " item" }, [ 2 ] ),
]
)


/*更新于2017/8/24   我发现这样传数据太麻烦了,而且我们如果从后台接收数据的应该是json对象的格式,用上面的方法一个个去new,还要自己转换一下,很麻烦,也不直观,修改如下,可以直接传入我们的json对象进行构建*/

function Element(obj){
this.tagName=obj.tagName;
this.props=obj.props;
var children=obj.children.map(function(item){
if(typeof item =="object")//如果包裹的是一个对象的话,继续new Element
{
item=new Element(item)
}
return item
})
this.children=children;
}

var jsonData={
tagName:"div",
props:{
class:"wrap"
},
children:[
{
tagName:"div",
props:{
class:"item"
},
children:["1"]
},
{
tagName:"div",
props:{
class:"item"
},
children:[
{
tagName:"div",
props:{
class:"item"
},
children:["12"]
}
]
},
]
}

Element.prototype.render = function () {
var el = document.createElement(this.tagName) // 根据tagName构建
var props = this.props

for (var propName in props) { // 设置节点的DOM属性
var propValue = props[propName]
el.setAttribute(propName, propValue)
}

var children = this.children || []

children.forEach(function (child) {
var childEl = (child instanceof Element)
? child.render() // 如果子节点也是虚拟DOM,递归构建DOM节点
: document.createTextNode(child) // 如果字符串,只构建文本节点
el.appendChild(childEl)
})

return el
}

var virtualDom=new Element(jsonData);
var realDom=virtualDom.render();
document.body.appendChild(realDom);


/*end*/

然后我们通过上面构造器函数得到的只是一个JS对象表示的DOM结构,然后我们再利用render方法去生成真正的DOM节点

Element.prototype.render = function () {
var el = document.createElement(this.tagName) // 根据tagName构建
var props = this.props

for (var propName in props) { // 设置节点的DOM属性
var propValue = props[propName]
el.setAttribute(propName, propValue)
}

var children = this.children || []

children.forEach(function (child) {
var childEl = (child instanceof Element)
? child.render() // 如果子节点也是虚拟DOM,递归构建DOM节点
: document.createTextNode(child) // 如果字符串,只构建文本节点
el.appendChild(childEl)
})

return el
}


ul.render();这个方法返回的对象才是我们真正的DOM节点,然后我们再把它appendchild到我们页面的真实DOM中即可

最后就是我们的DOM节点操作了,它会有一套自己的对比算法,就像我刚起提到的一样它内部已经做好了优化的算法(Diff算法),我们不用再去造轮子,我们要做的操作无非就是:

 1.替换掉原来的节点

 2.移动、删除、新增子节点

 3.修改了节点的属性

 4.对于文本节点,文本内容可能会改变。

大概思路:会在代码中定义好上面几种类型操作的名称,然后根据具体是哪个虚拟DOM的哪种操作,利用最优的算法,找出用这种方法实现后的DOM与之前的DOM的差异(由上面知道:初始化的时候它会创建一个“DOM树”,当我们想去修改的时候我们会创建一个新的"DOM树"),形成新的“DOM树”,最后把这个差异渲染到之前的DOM结构中去。

(这里寻找“DOM树上”具体的DOM节点是利用key这个属性,一般来说不用我们自己去设置,但是如果我们自己利用数组去动态渲染虚拟DOM的话就需要自己手动添加这个key值,我前面的博客中有提到过:http://blog.csdn.net/liuzijiang1123/article/details/66974630

综上所述:我前面提到的那些问题,我们都可以更加"智能"地去用虚拟DOM(其实就是我们用JS对象模拟的一种DOM结构)去批量处理。

而且React帮我们封装好了接口,再结合ES6我们开发起来就更加容易了,我们可以把每一个虚拟的DOM理解为一个class,即React中的一个组件,

我们做的只需要为这个组件定义属性和方法。

React会比手动更新DOM要快么?

网上查阅了解到:

其实React不会比手动更新DOM要快,因为其实它也是需要调用DOM API去操作DOM的,而且操作之前还有构建虚拟DOM和diff的过程。但是它高效维护状态和减少手动视图更新的同时达到了尽量少的DOM操作,权衡了代码可维护性和DOM性能。

有个贴切的比喻,把DOM和JavaScript各自想象为一个岛屿,它们之间用收费桥梁连接,js每次访问DOM,都要途径这座桥,并交纳“过桥费”,访问DOM的次数越多,费用也就越高。 因此,推荐的做法是尽量减少过桥的次数,努力待在ECMAScript岛上。因为这个原因react的虚拟dom就显得难能可贵了,它创造了虚拟dom并且将它们储存起来,每当状态发生变化的时候就会创造新的虚拟节点和以前的进行对比,让变化的部分进行渲染。

----------------------------------------------------------------------------------------------------------------------------------------------

二.React 组件,多组件嵌套,jsx内置表达式

对于这些概念我就不再详细说了,就直接看实例应该就能明白这些概念了。

(只给出关键性代码)

class HelloMessage extends React.Component {
constructor(props) {
super(props);
this.msg="Hello World"
}

render() {
return (
<div>
<h1>{this.msg}</h1>
</div>
);
}
};

ReactDOM.render(
<HelloMessage/>,
document.getElementById('app')
);


我这边就把这个程序梳理一下:

 1.创建一个HelloMessage类(首字母需要大写),继承React.Component(这个是React封装好的一个组件类,我们只需要继承它就可以用它提供的方法了,而且ES6提供了class和extends,这样我们创建类和继承就特别方便)。

 2.在es6里面我们创建类后里面的方法就不用再用fn:function 这种形式,直接fn( )即可,我们创建的属性都放在constructor( )这个方法(构造函数)里面。

 3.在constructor中我们需要在最开头使用super( ),super其实就是父类的构造函数,这个可以理解,因为我们使用了继承,所以当我们使用构造函数的时候,要先调用一下父类的构造函数,而且要把自己的参数全传给父类,这个props参数其实就是包含了组件上传进来的属性。

 4.rener方法中return出来的对象就是我们产生的真实DOM;这里面也用到了jsx语法,html标签可以嵌入到我们的js语言中,只不过在html标签中写js的时候需要用{ } 括起来,在写样式的时候需要两个{ } 例如<div style={ { "color":"red"} }>,还有就是里面的写法需要用驼峰式,例如onClick ,marginLeft等,还有就是有些和ES6冲突的写法需要换一种方式,比如calss属性要写成calssName,for需要写成htmlFor。

 5.render中return中只能有一个父级元素,不能有并列的。最后靠ReactDOM.render将我们生成的DOM节点真正挂载到我们的页面的DOM结构中去。

 6.render中renturn中返回的元素不完全是我们的html标签元素,还可以是我们自己定义例如,组件之间各级嵌套,就如我们的html元素一样。

return (
<div>
<h1>{this.msg}</h1>
<Component2>
<Component3/>
</Component2>
</div>
);

 

7.我们可以把页面细分成多个组件,例如我们<h1>,<p>都可以看成一个组件,也可以进行嵌套形成一个复杂的组件;其实我们可以看见React组件之间嵌套一层又一层,形如一个金字塔形的结构。每个组件分工不一样,有的只是做展示,有的则是有具体的功能和交互,组件之间互相配合,重复使用。

----------------------------------------------------------------------------------------------------------------------------------------------

三.State 属性、Props 属性、组件通信



React中最重要的两个概念,一个是组件,一个就是状态。我们是通过控制组件的状态从而控制程序的view层(都说React框架其实只是view层的表现,侧重点在于DOM节点的渲染).说到状态就不得不提state和props这2个属性。

这两个属性的最大不同点在于,props是只读的属性,state不是。由于这个特性我们大概就能猜出来这两个属性的作用来了:props是用来定义组件一些不变的属性,用来进行组件初始化的参数传递来达到渲染组件的目的,而state是定义一些可变属性,用来修改组件属性,从而达到再次渲染组件的目的。

这里既然说到了修改组件属性,那么我们就不得不提一下组件之间的通信:

可以参考我这篇博客的例子:http://blog.csdn.net/liuzijiang1123/article/details/64131547

这里还需要注意的是对于state属性的修改我们不能想当然的this.state.xxx=abc这样进行修改,react给了我们专门的方法this.setState( )这个方法供我们使用。

----------------------------------------------------------------------------------------------------------------------------------------------

四.生命周期



个人认为组件的生命周期同样是个很重要的概念。

首先说一下生命周期分为以下几个:(这些函数都是以回调函数的形式给我们,这样我们就只需要在这里面写我们的逻辑即可,不用去调用,去担心什么时候该调用这些函数)

componentWillMount( )
componentDidMount( ) (仅在客户端有效)
componentWillUpdate(object nextProps, object nextState)
componentDidUpdate(object prevProps, object prevState)
componentWillUnmount( )

此外,React 还提供两种特殊状态的处理函数。

componentWillReceiveProps(object nextProps):已加载组件收到新的参数时调用
shouldComponentUpdate(object nextProps, object nextState):组件判断是否重新渲染时调用<return true渲染组件,反之不渲染>

      当我们有这些生命周期函数后,首先在书写代码的逻辑上给了我们很大的一个帮助,好比告诉我们一个人每个阶段该做什么事情,这样就不至于出现"谁的青春不迷茫"这样的感慨了,对于我们代码的结构上有很大的帮助,方便我们以后去维护,查看问题。而且前面也说了它是以一个回调函数的形式给我们,我们只需要把逻辑往里面“丢”就行~

下面是一个组件的生命周期调用顺序:

      当一个组件被调用的时候先执行constructor这个函数,然后执行componentWillMount(即将渲染),接下来渲染到dom树上(触发render函数),渲染完成触发componentDidMount函数;

这时候,该组件就进入了一个running状态,并监视他的props和state以及被移除事件:

  当props发生变化时执行componentWillReceiveProps然后去判断是否需要重新渲染(shouldComponentUpdate),如果不需要则继续保持running状态;如果需要则如初始时一样,执行componentWillMount(即将渲染),接下来渲染到dom树上,渲染完成触发componentDidMount函数,保持running状态继续监视;

  当state发生变化时,则直接判断是否需要重新渲染(shouldComponentUpdate),然后根据是否需要决定执行渲染过程还是继续保持running状态;

  当该组件被移除(unmount)时,将直接执行componentWillUnmount,该组件从dom树上消失;

上面都是一些理论知识,然后我们看一下具体一个例子:

class Clock extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date()};
}

componentDidMount() {
this.timerID = setInterval( () => this.tick(),1000);
}

componentWillUnmount() {
clearInterval(this.timerID);
}

tick() {
this.setState({
date: new Date()
});
}

render() {
return (
<div>
<h2>It's {this.state.date.toLocaleTimeString()}</h2>
</div>
);
}
};
ReactDOM.render(
<Clock />,
document.getElementById('app')
);


这是一个时钟例子,利用定时器去更新数据,关键点在于我们的更新函数需要在组件挂载了之后执行,这样我们就应该写在componentDidMount中,当组件将要被卸载的时候消在componentWillUnmount中除定时器即可。

----------------------------------------------------------------------------------------------------------------------------------------------

五.组件的Refs

        jq之所以好用,很大一部分在于它强大的选择器,让我们更加轻松地直接操作我们想要的DOM节点;同样React也提供了一种让我们直接操作DOM节点的方法,其提供了一个refs属性,我们可以给组件定义一个ref=xxx
类似于我们ID,然后通过this.refs.xxx来直接操作我们的DOM节点,因为它返回的就是我们的真实DOM节点,这样我们就可以用原生的各种方法去操作它。
这里需要注意的两点就是:
 1.这个属性是添加在我们组件中的html标签中的,例如<p>,<div>等,并不是我们自己定义的标签
 2.需要等到组件componentDidMount之后我们才能访问到我们的DOM节点

       既然这里说到了jq,那我就顺便提一下,React的开放性特别好,在它里面可以用jq,而且还兼容其他的前端框架,angular,vue等等。因为React提供给我们的就两个东西,一个是组件,一个就是状态,其他的一些功能需要我们自己去组合开发,自己写业务逻辑。但是angular是不能兼容其他框架的,它本身也封装好了很多方法给我们使用,比如表单验证,angular就提供给了我们接口,而React需要我们自己想办法。
class RefsC extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date()};
}

componentDidMount() {
var _this=this;
$(_this.refs.lzj).click(function(){
_this.setState((prevState, props) => ({
num:prevState.num+1
}));
/*_this.setState(function(prevState){
return {
num:prevState.num+1
}
});*/
})
}
render() {
return (
<div>
<h1 ref="lzj">{this.state.num}</h1>
</div>
);
}
};
ReactDOM.render(
<RefsC />,
document.getElementById('app'));


(我们这里需要注意的一点就是this,一般的做法就是把当前的作用域this保存下来供后面使用,或者使用.bind去绑定,再或者使用箭头函数去解决)

/*目前就只先谈到这里,路由的话前面写过一次http://blog.csdn.net/liuzijiang1123/article/details/70800185*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: