[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进行必要的最少更新

公认的性能瓶颈

  1. React赢就赢在了最小化了重绘
  2. 并且避免了不必要的DOM操作

 

 

背后的思想:一步一步进化到 Redux


 

"React除了能够组件化开发ui,还完完全全实现了前后端的隔离"。客官,此话怎讲?

Ref: 怎样理顺react,flux,redux这些概念的关系,开发中有必要使用它们吗?

Ref: 理解 React,但不理解 Redux,该如何通俗易懂的理解 Redux?

 

  • 原始社会的问题

1. 反复刷新页面,尤其是内容复杂的页面,对浏览器的渲染性能消耗很大。

2. 由交互产生的很多细腻的前端数据,其实也很难交给后台处理,因为这是我们无处安放的临时状态

 

  • Ajax技术的出现
正是由于这两个显著缺陷,才导致ajax技术的出现。自从有了ajax通信和局部页面更新的机制以后,不用担心页面整体刷新导致的用户体验问题了!
于是前端开发大踏步地进入web2.0时代,大量交互细腻,内容丰富的SPA(single page application)也应运而生。

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思想的实现

 
server和client是远程通信的关系,因此为了尽量减少通信耦合,client每个操作的全部信息都以http请求的形式被概括成了精简的“作用量”(action)。
请求的url路径约定了用户的操作意图(当然RESTful概念中,请求的method也可以反映操作意图),
request参数表征了该“意图”的具体内容
正是基于这个action的抽象,client端的交互操作才可以被集中转移到server端的controller中做统一响应。

从代码层面而言,flux无非就是一个常见的event dispatcher,

其目的是要将以往MVC中各个View组件内的controller代码片断提取出来放到更加恰当的地方进行集中化管理,并从开发体验上实现了舒适清爽、容易驾驭的“单向流”模式。 

  

  • 怎样实现从view到state的反馈流程

用户在view上的交互行为(比如点击提交按钮等)应当引起state改变的时候,这个流程该怎么处理?


首先,react框架为我们理顺了 store --> view 的“单向”工作流store是state的容器);

然后,redux框架为我们理顺了 view --> store 的**“单向”**工作流。

并且,react和redux都以组件化的形式可以将各自负责的功能进行灵活地组装或拆分,最大程度上确保我们“一次只需要专注于一个局部问题”。具体来说,分为以下步骤:

    1. 单例store的数据在react中可以通过view组件的属性(props)不断由父模块**“单向”**传递给子模块,形成一个树状分流结构。如果我们把redux比作整个应用的“心肺” (redux的flux功能像心脏,reducer功能像肺部毛细血管),那么这个过程可以比作心脏(store)将氧分子(数据)通过动脉毛细血管(props)送到各个器官组织(view组件)
    2. 末端的view组件,又可以通过flux机制,将携带交互意图信息的action反馈给store。这个过程有点像将携带代谢产物的“红细胞”(action)通过静脉毛细血管又泵回心脏(store)
    3. action流回到store以后,action以参数的形式又被分流到各个具体的reducer组件中,这些reducer同样构成一个树状的hierarchy。这个过程像静脉血中的红细胞(action)被运输到肺部毛细血管(reducer组件)
    4. 接收到action后,各个child reducer以返回值的形式,将最新的state返回给parent reducer,最终确保整个单例store的所有数据是最新的。这个过程可以比作肺部毛细血管的血液充氧后,又被重新泵回了心脏
    5. 回到步骤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使用。这也是为什么要科里化的原因。

 
做好以上流程Redux和React就可以工作了。简单地说就是:
1. 顶层分发状态,让React组件被动地渲染。
2. 监听事件,事件有权利回到所有状态顶层影响状态。

 

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

接下来看看Redux/React与这个故事的联系:
  • view(React) = 家具的摆放在视觉的效果上
  • store(state) = 每个家具在空间内的坐标(如:电视的位置是x:10, y: 400)
  • action = 小明分配任务(谁应该干什么)
  • reducer = 具体任务都干些什么(把电视搬到沙发正对面然后靠墙的地方)

 

所以这个过程应该是这样的:

view ---> action ---> reducer ---> store(state) ---> view

如果放入一个web app中,
首先,store(state)决定了view,【预期效果】
然后,用户与view的交互会产生action,【根据效果做计划】
接着,这些action会触发reducer因而改变state,【计划实施】
最后,state的改变又造成了view的变化。【实施后的新效果】


 

都是函数式编程里的设计模式的东西,只是换了个马甲。

 

先了解下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>

 

posted @ 2018-04-11 22:43  郝壹贰叁  阅读(268)  评论(0编辑  收藏  举报