[React] 02 - Intro: why react and its design pattern
为啥使用React,给我个理由
过去
需要手动更新DOM、费力地记录每一个状态;既不具备扩展性,又很难加入新的功能,就算可以,也是有着冒着很大的风险。
不过,使用这种开发方式很难打造出极佳的用户体验。因为无论每次用户想要做点什么,都需要向服务端发送请求并等待服务端的响应,这会导致用户失去在页面上所积累的状态。
React
它引入了一种新的方式来处理浏览器DOM。
在任何时间点,React都能以最小的DOM修改来更新整个应用程序。
React本质上只关心两件事:1). 更新DOM;2). 响应事件。
- 只关心View
每次状态改变时,使用JavaScript重新渲染整个页面会非常慢,这应该归咎于读取和更新DOM的性能问题。React运用一个虚拟的DOM实现了一个非常强大的渲染系统,在React中对DOM只更新不读取。
React不处理Ajax、路由和数据存储,也不规定数据组织的方式。它不是一个Model-View-Controller框架。如果非要问它是什么,他就是MVC里的“V”。React的精简允许你将它集成到各种各样的系统中 。
- 工作状态
React以渲染函数为基础。这些函数读入当前的状态,将其转换为目标页面上的一个虚拟表现。
只要React被告知状态有变化,他就会重新运行这些函数,计算出页面的一个新的虚拟表现,接着自动把结果转换成必要的DOM更新来反映新的表现。【不需要reset方式的completely update】
这种方式看上去应该比通常的JavaScript方案——按需要更新每一个元素——要慢,但是React确实是这么做的:它使用了非常高效的算法,计算出虚拟页面当前版本和新版间的差异,基于这些差异对DOM进行必要的最少更新。
公认的性能瓶颈
- React赢就赢在了最小化了重绘。
- 并且避免了不必要的DOM操作。
背后的思想:一步一步进化到 Redux
"React除了能够组件化开发ui,还完完全全实现了前后端的隔离"。客官,此话怎讲?
Ref: 怎样理顺react,flux,redux这些概念的关系,开发中有必要使用它们吗?
Ref: 理解 React,但不理解 Redux,该如何通俗易懂的理解 Redux?
- 原始社会的问题
1. 反复刷新页面,尤其是内容复杂的页面,对浏览器的渲染性能消耗很大。
2. 由交互产生的很多细腻的前端数据,其实也很难交给后台处理,因为这是我们无处安放的临时状态
- Ajax技术的出现
1. 根据后台数据排版生成页面,现在只是最基本的工作。
2. 当用户进行某种交互操作引起页面状态变化之后,为了避免页面整体刷新,我们需要小心翼翼地将各个相关的页面局部元素拣选出来,做适当修改,再放回原处,动作一般不会太优雅。
- 从上面的例子我们得出两个经验
1. 根据确定的交互状态(state),一股脑儿决定页面的呈现(view),这种“单向流”的开发状态对程序员来说是思维清晰、比较轻松的;一旦我们需要不断手动更新view,并且改变state和view的代码还纠缠在一起,我们的内心往往是崩溃的。
2. 为了让前端开发不感到崩溃,就把所有state交给后台来维护,简单粗暴地通过重新加载页面来实现view的更新是不靠谱的,我们需要找到新的方法,来实现view的自动更新。
- “改变state,让view自动更新”的开发“触发”思想
我脑补着facebook的某个程序员在一个月黑风高的晚上坐在公司电脑前,抿了一口浓浓的咖啡,突然灵光一现,伴着屏幕上忽明忽暗的幽幽蓝光,在文本编辑器里写下这么一行文字:
可不可以把浏览器里的DOM tree克隆一份完整的镜像到内存,也就是所谓的“virtual DOM”,当页面的state发生变化以后,根据最新的state重新生成一份virtual DOM(相当于在内存里“刷新”整个页面),将它和之前的virtual DOM做比对(diff),然后在浏览器里只渲染被改变的那部分内容,这样浏览器的性能损耗和用户体验不就都不成问题了吗?
而我们知道在绝大部分网页应用中js引擎的性能和内存完全没有被充分利用,我们正好可以火力全开,利用js的这部分性能红利,实现内存中virtual DOM的diff工作,完美!
于是React横空出世!伴随着react的崛起,类似于redux这些专注于管理state的轻量级框架也变得炙手可热起来。
决定页面呈现的state可以通过模块属性(props)从父模块传递到子模块。
这种"树状"分流机制,有点像植物将养分(state)从根部不断运输到细枝末叶的过程。
- 架构的进化
* 传统MVC
1. 前端开发的Model相当于后台数据的镜像或缓存池,它和服务器端MVC中的Model概念一脉相承;
2. View对应页面的呈现,主要指的是和html、css相关的代码,它和服务器端MVC中的View概念也非常相近。
3. 显著的差别来自于controller:在后台应用中,用户和服务器之间的交互是通过http请求实现的,因此后台controller的表达形式是http请求的handler,并且和router(定义网站的url规则)紧密相关; 而前端应用中,用户和网页之间的交互主要是通过操作事件(例如点击鼠标、键盘输入等)实现的,因此前端的controller这里可以简单理解为各种交互事件的handler。
* 问题表象
修改Model的Controller代码像一把黄豆一样散落在了各个View组件的内部。
* 问题原因
如果可以用某种方式把这些散落的代码单独收拢到一起,是不是就让这可以让这张图示恢复秩序呢?好,我们顺着这个思路想下去。
不是MVC模式错了,而是我们压根缺少了一个和用户交互行为有关的action抽象!因此,对model的具体操作才没法从各个view组件中被剥离出来,放到一处。
* Flux思想 - 单向流思想
1. flux与react没有直接的关系,二者是完全独立的概念。
2. flux不是一个js库,而是一种前端代码的组织思想,比如说 redux库可以认为是一种flux思想的实现。
从代码层面而言,flux无非就是一个常见的event dispatcher,
其目的是要将以往MVC中各个View组件内的controller代码片断提取出来放到更加恰当的地方进行集中化管理,并从开发体验上实现了舒适清爽、容易驾驭的“单向流”模式。
- 怎样实现从view到state的反馈流程
用户在view上的交互行为(比如点击提交按钮等)应当引起state改变的时候,这个流程该怎么处理?
首先,react框架为我们理顺了 store --> view 的“单向”工作流(store是state的容器);
然后,redux框架为我们理顺了 view --> store 的**“单向”**工作流。
并且,react和redux都以组件化的形式可以将各自负责的功能进行灵活地组装或拆分,最大程度上确保我们“一次只需要专注于一个局部问题”。具体来说,分为以下步骤:
- 单例store的数据在react中可以通过view组件的属性(props)不断由父模块**“单向”**传递给子模块,形成一个树状分流结构。如果我们把redux比作整个应用的“心肺” (redux的flux功能像心脏,reducer功能像肺部毛细血管),那么这个过程可以比作心脏(store)将氧分子(数据)通过动脉毛细血管(props)送到各个器官组织(view组件)
- 末端的view组件,又可以通过flux机制,将携带交互意图信息的action反馈给store。这个过程有点像将携带代谢产物的“红细胞”(action)通过静脉毛细血管又泵回心脏(store)
- action流回到store以后,action以参数的形式又被分流到各个具体的reducer组件中,这些reducer同样构成一个树状的hierarchy。这个过程像静脉血中的红细胞(action)被运输到肺部毛细血管(reducer组件)
- 接收到action后,各个child reducer以返回值的形式,将最新的state返回给parent reducer,最终确保整个单例store的所有数据是最新的。这个过程可以比作肺部毛细血管的血液充氧后,又被重新泵回了心脏
- 回到步骤1
- 进一步熟悉React并理解Flux思想
从需求出发,看看使用React需要什么:
1. React有props和state:
props 意味着:父级分发下来的属性;
state 意味着:组件内部可以自行管理的状态;
并且整个React没有数据向上回溯的能力,也就是说数据只能单向向下分发,或者自行内部消化。
理解这个是理解React和Redux的前提。
2. 一般构建的React组件内部可能是一个完整的应用,它自己工作良好,你可以通过属性作为API控制它。但是更多的时候发现React根本无法让两个组件互相交流,使用对方的数据。
然后这时候不通过DOM沟通(也就是React体制内)解决的唯一办法就是提升state,将state放到共有的父组件中来管理,再作为props分发回子组件。
3. 子组件改变父组件state的办法只能是通过onClick触发父组件声明好的回调,
也就是父组件提前声明好函数或方法作为契约描述自己的state将如何变化,
再将它同样作为属性交给子组件使用。
这样就出现了一个模式:数据总是单向从顶层向下分发的,但是只有子组件回调在概念上可以回到state顶层影响数据。这样state一定程度上是响应式的。
4. 为了面临所有可能的扩展问题,最容易想到的办法就是把所有state集中放到所有组件顶层,然后分发给所有组件。
5. 为了有更好的state管理,就需要一个库来作为更专业的顶层state分发给所有React应用,这就是Redux。
让我们回来看看重现上面结构的需求:
a. 需要回调通知state (等同于回调参数) -> action【发起的通信请求】
b. 需要根据回调处理 (等同于父级方法) -> reducer 【对通信请求刷选处理的过程】
c. 需要state (等同于总状态) -> store
对Redux来说只有这三个要素:
a. action是纯声明式的数据结构,只提供事件的所有要素,不提供逻辑。
b. reducer是一个匹配函数,action的发送是全局的:所有的reducer都可以捕捉到并匹配与自己相关与否,相关就拿走action中的要素进行逻辑处理,修改store中的状态,不相关就不对state做处理原样返回。
c. store负责存储状态并可以被react api回调,发布action.
当然一般不会直接把两个库拿来用,还有一个binding叫react-redux, 提供一个Provider和connect。很多人其实看懂了redux卡在这里。
a. Provider是一个普通组件,可以作为顶层app的分发点,它只需要store属性就可以了。它会将state分发给所有被connect的组件,不管它在哪里,被嵌套多少层。
b. connect是真正的重点,它是一个科里化函数,意思是先接受两个参数(数据绑定mapStateToProps和事件绑定mapDispatchToProps),再接受一个参数(将要绑定的组件本身):
mapStateToProps:构建好Redux系统的时候,它会被自动初始化,但是你的React组件并不知道它的存在,因此你需要分拣出你需要的Redux状态,所以你需要绑定一个函数,它的参数是state,简单返回你关心的几个值。
mapDispatchToProps:声明好的action作为回调,也可以被注入到组件里,就是通过这个函数,它的参数是dispatch,通过redux的辅助方法bindActionCreator绑定所有action以及参数的dispatch,就可以作为属性在组件里面作为函数简单使用了,不需要手动dispatch。这个mapDispatchToProps是可选的,如果不传这个参数redux会简单把dispatch作为属性注入给组件,可以手动当做store.dispatch使用。这也是为什么要科里化的原因。
2. 监听事件,事件有权利回到所有状态顶层影响状态。
--------------------------------------------------------------------------------------------------------
- view(React) = 家具的摆放在视觉的效果上
- store(state) = 每个家具在空间内的坐标(如:电视的位置是x:10, y: 400)
- action = 小明分配任务(谁应该干什么)
- reducer = 具体任务都干些什么(把电视搬到沙发正对面然后靠墙的地方)
所以这个过程应该是这样的:
view ---> action ---> reducer ---> store(state) ---> view
如果放入一个web app中,
都是函数式编程里的设计模式的东西,只是换了个马甲。
先了解下MVP模式:[Android Module] 03 - Software Design and Architecture
Presenter的角色就是:从View收集loadData这样的请求,然后交由Model的一个具体对应的函数去执行。
React 组件 API
React 组件 API。我们将讲解以下7个方法:
- 设置状态:setState
- 替换状态:replaceState
- 设置属性:setProps
- 替换属性:replaceProps
- 强制更新:forceUpdate
- 获取DOM节点:findDOMNode
- 判断组件挂载状态:isMounted
例子:设置状态:setState
<body>
<div id="message" align="center"></div> <script type="text/babel">
var Counter = React.createClass({
getInitialState: function () {
return { clickCount: 0 };
},
handleClick: function () {
this.setState( function(state) {
return {clickCount: state.clickCount + 1};
} );
},
render: function () {
return (<h2 onClick={this.handleClick}>点我!点击次数为: {this.state.clickCount}</h2>);
}
});
ReactDOM.render(
<Counter />,
document.getElementById('message')
);
</script>
</body>
React 组件生命周期
组件的生命周期可分成三个状态:
- Mounting:已插入真实 DOM
- Updating:正在被重新渲染
- Unmounting:已移出真实 DOM
生命周期的方法:
componentWillMount 在渲染前调用,在客户端也在服务端。
componentDidMount : 在第一次渲染后调用,只在客户端。之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。 如果你想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异部操作阻塞UI)。
componentWillReceiveProps 在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。
shouldComponentUpdate 返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。 可以在你确认不需要更新组件时使用。
componentWillUpdate在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。
componentDidUpdate 在组件完成更新后立即调用。在初始化时不会被调用。
componentWillUnmount在组件从 DOM 中移除的时候立刻被调用。
React 表单与事件
1. 在输入框值发生变化时我们可以更新 state。
var HelloMessage = React.createClass({
getInitialState: function() {
return {value: 'Hello Runoob!'};
},
------------------------------------------------------
handleChange: function(event) {
this.setState({value: event.target.value});
},
------------------------------------------------------
render: function() {
var value = this.state.value;
return <div>
<input type="text" value={value} onChange={this.handleChange} /> // 监听到value发生了变化,触发handleChange.
<h4>{value}</h4>
</div>;
}
});
ReactDOM.render(
<HelloMessage />,
document.getElementById('example')
);
2. 在子组件上使用表单。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>菜鸟教程 React 实例</title>
<script src="https://cdn.bootcss.com/react/15.4.2/react.min.js"></script>
<script src="https://cdn.bootcss.com/react/15.4.2/react-dom.min.js"></script>
<script src="https://cdn.bootcss.com/babel-standalone/6.22.1/babel.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel"> var Content = React.createClass({
render: function() {
return <div>
<input type="text" value={this.props.myDataProp} onChange={this.props.updateStateProp} />
<h4>{this.props.myDataProp}</h4>
</div>;
}
}); -- 以上是子组件 --
======================================================================
-- 以下是父组件 --
var HelloMessage = React.createClass({
getInitialState: function() {
return {value: 'Hello Runoob!'};
},
----------------------------------------------------------------------
handleChange: function(event) {
this.setState({value: event.target.value});
},
----------------------------------------------------------------------
render: function() {
var value = this.state.value;
return <div>
<Content myDataProp = {value}
updateStateProp = {this.handleChange}>
</Content>
</div>;
}
});
ReactDOM.render(
<HelloMessage />,
document.getElementById('example')
);
</script>
</body>
</html>
React 事件
1. 通过 onClick 事件来修改数据:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>菜鸟教程 React 实例</title>
<script src="https://cdn.bootcss.com/react/15.4.2/react.min.js"></script>
<script src="https://cdn.bootcss.com/react/15.4.2/react-dom.min.js"></script>
<script src="https://cdn.bootcss.com/babel-standalone/6.22.1/babel.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
-------------------------------------------------------------------
var HelloMessage = React.createClass({
getInitialState: function() {
return {value: 'Hello Runoob!'};
},
-------------------------------------------------------------------
handleChange: function(event) {
this.setState({value: '菜鸟教程'})
},
--------------------------------------------------------------------
render: function() {
var value = this.state.value;
return <div>
<button onClick={this.handleChange}>点我</button>
<h4>{value}</h4>
</div>;
}
});
ReactDOM.render(
<HelloMessage />,
document.getElementById('example')
);
</script>
</body>
</html>
2. 从子组件中更新父组件的 state 时,你需要在父组件通过创建事件句柄 (handleChange) ,并作为 prop (updateStateProp) 传递到你的子组件上。
<body>
<div id="example"></div>
<script type="text/babel">
var Content = React.createClass({
render: function() {
return <div>
<button onClick = {this.props.updateStateProp}>点我</button>
<h4>{this.props.myDataProp}</h4>
</div>
}
});
-- 以上是子组件 --
=======================================================================================
-- 以下是父组件 --
var HelloMessage = React.createClass({
getInitialState: function() {
return {value: 'Hello Runoob!'};
},
---------------------------------------------------------------------------------------
handleChange: function(event) {
this.setState({value: '菜鸟教程'})
},
---------------------------------------------------------------------------------------
render: function() {
var value = this.state.value;
return <div>
<Content myDataProp = {value}
updateStateProp = {this.handleChange}></Content>
</div>;
}
});
ReactDOM.render(
<HelloMessage />,
document.getElementById('example')
);
</script>
</body>
[React] 02 - Intro: why react and its design pattern的更多相关文章
- [React] 01 - Intro: javaScript library for building user interfaces
教学视频: http://www.php.cn/code/8217.html React 教程: http://www.runoob.com/react/react-tutorial.html 本篇是 ...
- [React] 03 - Intro: react.js in twelve demos
Ref: React 入门实例教程 这算什么,react学习例子的十二门徒?哈哈 如何运行别人的react项目? Ref: [React全家桶入门之CODE]项目代码与使用方法 使用git克隆项目到本 ...
- react基础学习和react服务端渲染框架next.js踩坑
说明 React作为Facebook 内部开发 Instagram 的项目中,是一个用来构建用户界面的优秀 JS 库,于 2013 年 5 月开源.作为前端的三大框架之一,React的应用可以说是非常 ...
- 【React】- 1、React介绍
React的开发背景 构建数据不断变化的大型应用 大量DOM操作 <---- 自动DOM操作 数据变化 逻辑及其复杂 <---- 状态对应内容(自动变化) 特点: - 简单 ...
- 【react学习】关于react框架使用的一些细节要点的思考
( _(:3 」∠)_给园友们提个建议,无论是API文档还是书籍,一定要多看几遍!特别是隔一段时间后,会有意想不到的收获的) 这篇文章主要是写关于学习react中的一些自己的思考: 1.set ...
- React Native 系列(二) -- React入门知识
前言 本系列是基于React Native版本号0.44.3写的,最初学习React Native的时候,完全没有接触过React和JS,本文的目的是为了给那些JS和React小白提供一个快速入门,让 ...
- React-Native(三):React Native是基于React设计的
React Native是基于React js设计的. 参考:<React 入门实例教程> React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript ...
- 1. React介绍 React开发环境搭建 React第一个程序
什么是 React React 是 Facebook 发布的 JavaScript 库,以其高性能和独特的设计理念受到了广泛关注. React的开发背景 Faceboo ...
- 转载 React.createClass 对决 extends React.Component
先给出结论,这其实是殊途同归的两种方式.过去我们一般都会使用 React.createClass 方法来创建组件,但基于 ES6 的小小语法糖,我们还可以通过 extends React.Compon ...
随机推荐
- javax.crypto.BadPaddingException: Given final block not properly padded解决方案
解密的时候报错: javax.crypto.BadPaddingException: Given final block not properly padded 该异常是在解密 ...
- Java int转string 长度不足左补0
最近项目中用到这个需求,我试了两个方法都可以实现 方法一:(推荐) String s=String.format("%010d", 123)//123为int类型,0代表前面要补的 ...
- StackExchange.Redis 管道 批量 高性能插入数据
现在用redis来做数据缓存的越来越多了,很多项目都有初始化redis数据的过程,由于初始化的数据比较大,那么该过程越快越好.这里我们以HashSet方法为例, 这里我们推荐用HashEntry[] ...
- 从Encoder到Decoder实现Seq2Seq模型
https://zhuanlan.zhihu.com/p/27608348 更新:感谢@Gang He指出的代码错误.get_batches函数中第15行与第19行,代码已经重新修改,GitHub已更 ...
- virtualbox centos安装增强工具和Centos与VirtualBox共享文件夹设置
VirtualBox 大家都习惯性把它简称为 Vbox ,比 VM 的体积小.开源.速 度快.不过在使用 VirtualBox 在虚拟机中安装 CentOS 之后,不能直接运行安装好 Virtual ...
- 天猫魔盒1代TMB100E刷机, 以及右声道无声的问题
这个是在小米盒子1代之后买的, 当时速度比小米盒子快, 除了遥控器比较软, 电池盖不太对得齐以外, 用起来还不错. 但是时间长了之后总是不停自己升级, 自己安装一些应用, 还删不了, 要知道这个盒子的 ...
- Spark初识
一.简介 1.什么是Spark 官网地址:http://spark.apache.org/ Apache Spark™是用于大规模数据处理的统一分析引擎. 从右侧最后一条新闻看,Spark也用于AI人 ...
- GCD 之线程死锁
GCD 确实好用 ,很强大,相比NSOpretion 无法提供 取消任务的功能. 如此强大的工具用不好可能会出现线程死锁. 如下代码: - (void)viewDidLoad { [super vie ...
- Jenkins Post Build网址
Hudson Post build taskhttps://plugins.jenkins.io/postbuild-taskThis plugin allows the user to execut ...
- Ubuntu图形界面环境下启动应该程序:
1.先说下Ubuntu14.04系统开机紫框的问题: Grub theme:黑色屏幕出现紫色边框 There's a minor typo on the grub theme which produc ...