来自 GitChat 做者:余博伦
更多IT技术分享,尽在微信公众号:GitChat 技术杂谈html
Facebook 官方对 React 定义是:用来构建用户界面的库(Library)。注意到这里的用词是库(Library)而不是框架(Framework)。React 不像早期版本的 Angular 这样功能很是完备的 mvvm 框架,它主要只专一于解决 MVC 当中 V 层,也就是视图层(View)方面的问题。react
不过咱们也没必要太过纠结库(Library)或框架(Framework)的定义。复杂的,给出你一整套解决方案的就叫框架(Framework);简单的,专一解决一个问题并作到极致(Do one thing and do it well)的就叫库(Library)。git
不过咱们仍是习惯性地称 React 是一个 JavaScript 框架,由于除了 React 核心库自己,在 React 的生态圈当中,还有不少其余能够搭配协同的工具库,好比在此次分享当中咱们要介绍的用来解决状态管理问题的 Redux ;用来提供前端路由功能的 react-router 。咱们把这些工具库统称为 React 技术栈,组合使用 React 技术栈也就彻底撑得起一个框架提供的功能了。github
声明式npm
说白了声明式就是你告诉程序你要一个什么样的东西的编写代码的方式。这也是在开发构建用户界面时最友好的方式。在 React 当中,你能够很轻松地告诉 React 你想要一个什么样的界面。咱们使用一种叫作 JSX 的相似于 HTML/XML 的 JavaScript 语法扩展来和 React 交流:redux
<应用> <输入框></输入框> <按钮></按钮> </应用>
这就好像咱们能够直接对 React 说:后端
这里我要一个按钮!
这里我要一个表单!react-native
是否是很是的直观明了呢?前端工程化
组件化
在 React 当中,咱们是以组件(Component)的概念来划分用户界面的。一般咱们开发的页面均可以拆成一个个通用的组件,例如导航、表单、列表项、页脚等等。
使用 React 能够在很大程度上提升你代码的可复用性,编写页面就如搭积木通常简单:
<应用> <导航/> <注册表单/> <页脚/> </应用>
这也一样意味着,咱们除了能够在开发页面时复用本身编写的组件之外,还能把别人编写好的通用组件直接拿过来用。自定义一下样式,传个数据进去,组合起来,一个页面分分钟就搞定。
一次学习,随处编写
React 最强大的地方在于,其内部实现的虚拟DOM屏蔽了全部的底层实现,经过不一样的渲染器(renderer),你编写的同一套代码能够用来构建包含 浏览器/桌面操做系统/Android/iOS 等几乎全部平台的用户界面。也就是说,掌握了 React 以后,你的能力将不止局限于写网页,而是能够在几乎全部的平台上开发用户界面。
这也就是为何咱们使用 React 的时候须要调用两个库 react 和 react-dom,react 库文件用来实现 React 的核心功能,react-dom 则用来把它渲染到浏览器当中。
目前已有的其余平台的解决方案还包括:
例如在使用 React Native 的时候,咱们一样是使用 react 核心库来实现基础功能,而后经过 react-native 库将咱们编写的界面渲染到移动端上。
也就是说,有了 React 以后,咱们能够用一种统一的描述方式来开发用户界面,至于在什么平台上实现,只要有相应的渲染器(renderer),咱们就可以把咱们开发的界面在对应的平台上面渲染出来。例如在美剧《西部世界》当中,React 甚至能够用来编写人工智能 Host 的故事线:
React 组件
咱们用 React 编写的代码绝大多数都是组件的代码。编写 React 组件须要遵循 React 内部的一系列规范,所以用 React 编写出来的应用自带前端工程化属性。无论新手仍是老司机,只要是用 React 写组件,咱们都能保证他写出来的代码是差很少的。这也就很是有利于一个项目组当中多个开发者之间进行协做。很是适合高级作架构,中级封组件,初级写业务的模式。
React 组件其实就至关于 JavaScript 当中的一种函数,接受应用数据做为参数,内部进行一系列处理(包含事件处理函数、生命周期函数等,此处不展开讲),返回一个 React 元素。
React 元素
这里要注意到,React 组件和 React 元素是两个不一样的概念。React 元素是 React 组件的一部分,也就是 React 组件返回的要拿来渲染的内容。
在 React 当中,咱们经过一种叫作 JSX 的 JavaScript 语法扩展来描述 React 元素。
const title = <h1>Counter</h1>;
这里特别要注意的是,JSX 既不是原生的 HTML,也不是 jQuery 当中的字符串 $('<h1>Counter</h1>')
,更不是 pug(jade) 当中的模板 h1 Counter
。这是 React 内部本身的一套实现,能够容许你像写 HTML 同样,在 JavaScript 代码当中直接写页面,React 会在随后的渲染过程中自动把 JSX 转译成页面当中真实的 DOM 元素。
在 React 当中,有两种定义组件的方式。(注:在 react@15.6 当中已经废弃了 createClass
方法,若是你历来没用过 React 请自动忽略)
函数定义组件
比较简单的一些,只接受外部传入的数据的组件,咱们通常经过函数定义的方式来编写:
var Button = function(props) { return <button onClick={props.onClick}>+</button>; } // 固然也能够用 ES6 的 箭头函数 arrow function const Number = ({ number }) => <p>{number}</p>;
props & state
上述示例当中的 props 就是组件数据的一种。在 React 当中,最经常使用的组件数据有两种:props 和 state.
其中 props 是从外部传入的,内部没法修改,用来渲染展现的数据。
而 state 则是组件内部维护,能够跟随应用状态改变而改变的数据(例如用户输入的表单项)。
类定义组件
比较复杂的,须要处理事件,调用生命周期函数,与服务器交互数据的组件,咱们经过类定义组件的方式来声明:
// 从 React 库当中获取组件的基础支持 const { Component } = React; // 使用 ES6 当中的 class 关键字来声明组件 class Container extends Component { /* 类中的构造方法,调用super方法来确保咱们可以获取到this,组件自身的 state 数据也在构造方法当中初始化。*/ constructor() { super(); this.state = { number: 0 } } /* 事件处理方法,在 React 当中咱们经过调用 `setState` 方法来修改 state 数据,这样才能触发组件在界面当中自动从新渲染更新 */ handleClick() { this.setState({number: this.state.number+1}); } // 渲染方法,返回 React 元素 render() { return ( <div> <Title /> <Number number={this.state.number} /> <Button onClick={() => this.handleClick()} /> </div> ); } }
在本文的开头咱们已经介绍过了,React 是一个视图层的框架,也就是说它只有 V,而真正在编写前端代码的时候,除了页面展现的内容之外,咱们还须要进行处理用户输入、验证表单、和服务器进行数据交互之类的操做。
那么在实际的编码过程中,咱们要如何解耦这些应用的业务逻辑和用户界面的结构样式呢?
这时咱们就须要引入一组展现组件和容器组件的概念。
展现组件
容器组件
例如:
// 展现组件 const Button = props => <button onClick={props.onClick}>+</button>; // 容器组件 class Counter extends Component { constructor() { super(); this.state = { number: 0 } } handleClick() { this.setState({number: this.state.number + 1}); } render() { return ( <div> <Title /> <Number number={this.state.number} /> <Button onClick={() => this.handleClick()} /> </div> ) } }
在上述 React 部分的介绍当中,咱们已经提到了,React 用来处理数据的方式主要有 props 和 state 两种(另外还有一种不经常使用的 context)。
其中的 props 必须是从父组件传递到子组件,若是嵌套层级不少,props 必须逐级从保存数据的组件层层传递到使用 props 的组件当中。而 state 在使用的时候,必须经过调用 this.setState()
方法,在改变 state 值的同时,触发 React 组件运行的生命周期,来触发界面的更新。 this.setState()
方法能够传递数据、方法、回调函数。在同一次操做中,连续调用屡次 this.setState()
方法也会形成许多难以预料的结果,仅仅经过看代码你很难判断出最后值会变成什么。
而咱们使用 React 开发界面的主要场景是在Web应用当中,不一样于传统的之内容为主的网页。Web应用涉及到很是多的状态数据的改变,包括用户的交互、服务器通讯、界面的动画、样式的改变等等内容。
咱们在开头也提到了,React 是一个专一于视图层的库,在数据的改变,状态管理方面,它并无作得很好。所以,当咱们的应用复杂到必定程度时,就须要引入一些其余的工具库来帮助咱们解决状态管理的问题。
Component(state) = View
咱们经过一个简单的公式来讲明这个问题。在 React 的理念当中,组件其实就是一个方法,咱们向组件方法传入数据得出要渲染的视图内容。
这里之因此把传入的数据称为 状态(state) ,是由于在一个应用当中,许多数据都是处于变化当中的,根据用户的不一样操做响应发生改变(好比说在咱们计数器的示例当中,点击按钮,计数器的数字就会随之改变增长)。
也就是说,咱们看到的视图,网页的内容,若是把应用状态数据不一样的改变不一样的时刻看做是一段动画的话,页面在某一刻显示的内容其实就是动画的某一帧。
因此,咱们在开发构建界面时,除了界面的样式和逻辑之外,如何处理状态数据就成了另一个咱们须要主要关注的问题。这一问题的解决方案,天然也就叫作状态管理了。
React 应用的开发理念告诉咱们,在一个应用当中,若是有两个组件须要使用同一数据,那么咱们须要把这一组数据提高到它们共同的父组件当中保存;在实际开发当中,应该尽可能控制有状态组件(含有 state 的组件)的数量。
在一个Web应用当中,会涉及到显示数据的增删改查、服务器数据获取、界面切换显示内容等各类各样类型的状态数据改变。
那么咱们为何不把全部的状态数据改变,用一种统一的方式描述;既然要控制有状态组件的数量,那么咱们为何不干脆直接把一个应用的全部状态数据存储在一个统一的地方集中管理?
这也就是 Redux 的理念。
Action 就是咱们上述的,用统一的形式,描述全部改变应用状态数据的操做的方法。说白了,它其实就是一个带有 type
属性的 Javascript 对象:
{ type: 'INCREMENT', value: 1 }
例如在咱们的计数器当中,点击按钮数字增长1的操做能够用上述格式内容的对象来描述表示。Redux 对 Action 的要求并非很是严格,你只须要保证它包含 type
属性,其他的内容彻底由你本身决定。固然若是你但愿你制定的 Action 更加符合规范,能够遵循 Flux Standard Action 标准。
Reducer 则是 Redux 的设计理念当中最核心的方法,它接受当前的状态数据以及触发的 Action 做为参数,根据内部 switch
结构的逻辑判断,返回一个新的状态数据:
(previousState, action) => newState
例如在咱们的计数器当中,能够抽象编写出这样一个 Reducer 方法:
function counter(state = 0, action) { switch (action.type) { case 'INCREMENT': return state + action.value default: return state } }
咱们能够看到 counter 函数接受 state 和 action 两个参数,返回值则是通过一个 switch 结构判断的新的 state 数据。这样结构的函数也就是咱们在使用 Redux 时编写的 Reducer 方法。
这是 Redux 理念当中最核心的一个部分,它决定了一个应用当中的状态数据在不一样的 Action 被触发时具体会如何改变。
有关 Reducer 的更详细解释,能够参阅我以前发表的 Redux 中的 reducer 究竟是什么,以及它为何叫 reducer 一文。
Store 则是 Redux 当中咱们用来存储状态数据的地方,它提供了3个主要的方法:
getState()
dispatch(action)
subscribe(listener)
而在使用 Redux 时,咱们能够经过它提供的 createStore
方法,直接从 reducer 函数生成对应的 store :
const { createStore } = Redux; const store = createStore(counter);
咱们能够直接在 React 项目当中使用 Redux:
// 把以前 React 的渲染函数命名为 render const render = () => { /* 传入 store.getState() 获取 Redux 当中存储的状态数据 * 传入 store.dispatch() 方法来执行对应 action 修改状态数据 */ ReactDOM.render(<Counter number={store.getState()} onIncrement={() => store.dispatch({ type: 'INCREMENT', value: 1 })} />, document.getElementById('root')); } // 调用一次 render 方法进行初次渲染 render() // 使用 store.subscribe 方法订阅 render 这样每次 store.dispatch 方法触发时就会自动调用 render store.subscribe(render);
固然,每次 Redux 当中的状态数据改变时都强制执行 ReactDOM 的 render
方法并非最优选择。事实上,社区已经开发出了一个名为 react-redux 的库专门来辅助咱们对 React 和 Redux 进行协同使用。
/* Provider 充当为整个 React 应用传入 Redux 当中 store 的容器组件 * connect 用来为须要使用 store 的组件提供相应的状态数据或 dispatch 方法 */ const { Provider, connect } = ReactRedux; /* 咱们经过 mapStateToProps 来将 Redux 当中的状态数据映射到 React 相应的 props 当中 */ const mapStateToProps = state => ({ number: state }); class Counter extends Component { constructor(props) { super(props); } handleClick() { // 在这里调用传入组件的 dispatch 方法 this.props.dispatch({ type: 'INCREMENT', value: 1 }); } render() { return ( <div> <Title /> <Number number={this.props.number} /> <Button onClick={() => this.handleClick()} /> </div> ) } } /* 咱们须要经过 connect 方法来包装一下 React 的 Counter 组件,使其获取到 Redux 的 store 当中的方法和数据 */ Counter = connect(mapStateToProps)(Counter);
react-router 是 React 生态圈当中前端路由功能的实现。它最大的特色是能够不用添加额外的路由配置文件,像使用全部其余 React 组件的方式同样,只须要引入几个组件就能够轻松为你的 React 应用添加前端路由的功能。
咱们都知道,在传统的网站当用,一个 URL 就对应着某个特定的页面。当咱们在浏览器地址栏当中输入这个 URL 的时候,浏览器就会从网站的服务器请求该页面,获取相应的内容。
而在Web应用的开发当中,咱们能够经过操纵浏览器暴露给咱们的 history 接口以及异步服务器数据请求等方式,在前端就实现路由的切换,而不须要每次都让服务器后端解析 URL 路由请求再返回内容。
使用前端路由能够很大程度上提高Web应用,尤为是单页面应用的使用体验。
react-router 的使用很是简单,它目前已经发行到了 v4 版本,而以前的3个版本在网络上也能找到很是多的应用。在这里咱们仅拿最新的版本做为示例。在 react-router@4 版本当中,专门为Web端提供了高度封装好的 react-router-dom 库,这下咱们几乎不须要任何的配置就能够直接使用前端路由功能了:
/* 这里引入的3个方法所有都是封装好的 React 组件,使用方法和其余 React 组件几乎没有任何差异 */ const { HashRouter, Route, Redirect } = ReactRouterDOM;
HashRouter
为咱们的应用提供了 hash 形式(也就是带#的路由)路由的功能支持。
{/* 在一般状况下,咱们不须要为 HashRouter 进行任何设置,直接引入使用便可。 */} <HashRouter> <App/> </HashRouter>
主要到在 react-router 提供的全部类型的 Router
组件当中,第一级的子组件有且只能有一个。所以咱们在使用的时候,一般在咱们应用组件的最外层包裹上一个 <div>
标签:
<HashRouter> <div> ... </div> </HashRouter>
这里为了方便在线演示,因此咱们使用了 HashRouter
组件,在实际的开发当中,更常用的是 BrowserRouter
组件,它能够为咱们提供不带 #
的前端路由支持,更加友好。
前端路由的主要功能就是经过判断不一样的浏览器地址显示不一样的内容,那么具体某个路由地址要怎么展现某个组件呢?
这就是 Route
组件为咱们提供的功能:
<HashRouter> <div> <Route path='/:title?' component={App} /> </div> </HashRouter>
其中的 path 属性用来设置匹配的目标路由地址,路由地址能够是固定的字符串,例如 home/about/user 之类的,也能够像咱们示例中同样,以冒号开头将路由的地址做为参数,以后咱们能够在组件当中获取到对应的路由参数(以 ?
结尾则表示这一参数是可选的):
const Title = props => <h1>{props.title}</h1>; const App = ({ match }) => ( <Provider store={store}> <Counter title={match.params.title} /> </Provider> );
这里的 match.params.title
也就是咱们路由参数当中对应的值了。
有了前端路由的内容,咱们还须要相应的前端路由的导航。前端路由导航的主要功能是实现浏览器地址栏 URL 的切换,并触发Web应用展现对应的内容,而不是像原生的 HTML 超连接试图向服务器发起对应 URL 的请求。
react-router 一样为咱们提供了现成的 Link
导航组件:
<HashRouter> <div> <ul> <li><Link to='/react'>react</Link></li> <li><Link to='/redux'>redux</Link></li> <li><Link to='/react-router'>react-router</Link></li> </ul> <Route path='/:title?' component={App} /> </div> </HashRouter>
直接在浏览器中使用
为了方便咱们在线演示,更快地直接上手,在本文的示例当中,咱们均采用了直接在浏览器当中使用这些库的方法。在 Codepen 示例当中,我已经事先引入了全部库的 CDN 文件,这些库都会向页面暴露一个全局的对象,而后咱们能够经过解构赋值的方式,获取到对象当中咱们要使用的方法,例如:
const { Component } = React;
若是你是在本地进行练习,也能够经过 <script>
标签引入相应库的 CDN 文件,以后经过相同的方式进行调用。
P.S. 若是你使用最新版的 Chrome 进行调试,这些 ES6 的新特性均可以直接在浏览器当中运行,无需编译。
经过 npm 来使用
在正式的开发项目当中,咱们会使用 npm 来管理安装各个库,以后经过 import
的方式来调用:
首先安装:
npm install react react-dom --save
而后调用:
import React from 'react';
本文全部代码完整示例能够在 GitChat React Examples 在线查看调试。包含: