客户端高性能组件化框架React简介、特点、环境搭建及常用语法

【本文源址:http://blog.csdn.net/q1056843325/article/details/54729657 转载请添加该地址】

明天就是除夕了
预祝大家新春快乐 [ ]~( ̄▽ ̄)~*
天天饭局搞得我是身心疲惫= =
所以更新比较慢
今天想跟大家分享的就是这个大名鼎鼎的React框架

简介

React是近两年非常流行的框架
流行到什么程度呢?
我看了一下Github上的数据

客户端高性能组件化框架React简介、特点、环境搭建及常用语法_第1张图片

React达到了5w8+的star
在JavaScript中star排名第4
受欢迎程度可见一斑

感兴趣的同学,给大家设置一个传送门:Github-JavaScript-most stars


React并不难,还是挺容易上手的
起源于Facebook内部项目(一个广告系统)

客户端高性能组件化框架React简介、特点、环境搭建及常用语法_第2张图片

传统页面从服务器获取数据,显示到浏览器上,用户输入数据传入服务器
但随着数据量增大,越来越难以维护了
Facebook觉得MVC不能满足他们的扩展需求了(巨大的代码库和庞大的组织)
每当需要添加一项新功能或特性时,系统复杂度就呈几何增长
致使代码脆弱不堪、不可预测,结果导致他们的MVC正走向崩溃
当系统中有很多的模型和相应视图时,其复杂度就会迅速扩大,非常难以理解和调试

总之就是Facebook对市场上所有JS-MVC框架都不满意,认为都不适合大规模应用
就自己写了一套,用来架设Instagram网站
写完后用着用着,发现哎呦这货还真是不错,然后就开源了
随着这几年的沉淀,React已经变得越来越强大了
值得我们去了解~

MVC

科普一下MVC
MVC是一种软件架构模式(后端影响到了前端)
MVC就分为M、V、C三部分

  • Model(模型):
    应用程序中用于处理应用程序数据逻辑的部分,通常负责在数据库中存取数据
  • View(视图):
    应用程序中处理数据显示的部分,通常依据模型数据创建
  • Controller(控制器):
    应用程序中处理用户交互的部分,通常负责从视图读取数据,控制用户输入,并向模型发送数据

客户端高性能组件化框架React简介、特点、环境搭建及常用语法_第3张图片

简单的理解一下
我们就是user,在页面中点击了一个按钮触发了事件
控制器Controller调整数据,模型Model中数据改变
数据改变又会导致视图View更新
UI的改变反馈呈现给我们user


这里我要特别说明一下,虽然这里介绍了MVC
但是不能说React就是MVC框架
可以说React是用于构建组件化UI的库,是一个前端界面开发工具
它可以作为MVC中的View视图部分

框架特点

React它具有以下特点

  • 高性能
    传统web页面操作DOM涉及重绘重排相当耗性能
    React 提供了一种不同而又强大的方式来更新Dom(轻量级虚拟Dom——Virtual Dom),代替直接操作DOM
    更新virtual dom时不一定马上影响真实Dom,React会等到事件循环结束
    利用Diff算法,通过当前新Dom表述与之前做比较,计算出最小步骤来更新真实Dom
  • 组件化
    Dom树上的节点称为元素,而虚拟Dom 的节点称作组件(可复用性)
    (组件的特点下面还会谈到)
  • 可预测性
    state属性包含定义组件所需要的一些数据,当数据发生变化时,将会调用render重现渲染
    React 把组件看成是一个状态机(State Machines)
    通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致
  • 单向数据流
    数据从父节点传递到子节点,只需要从父节点获取props渲染即可
    (往下看就理解了)

环境搭建

我选择使用webpack搭建环境,当然其他工具也可以
这是我的webpack.config.js配置文件

module.exports = {
    entry: {
        index: './src/js/entry.js'
    },
    output: {
        path: './static/dist/',
        publicPath: 'http://localhost:8080/static/dist/',
        filename: '[name].js'
    },
    module: {
        loaders: [
            {
                test: /\.js$/,
                loader: 'babel',
                query: {
                    presets: ['react', 'es2015']
                }
            },
            {
                test: /.less$/,
                loader: 'style!css!less'
            }
        ]
    }
}

这里的关键就是除了要安装babel-loaderbabel-core
还要安装babel-preset-es2015babel-preset-react
用于解析ES6语法和React的JSX语法
还有reactreact-dom也是必须要下载的依赖


全部依赖模块在这里

"devDependencies": {
  "babel-core": "^6.22.1",
  "babel-loader": "^6.2.10",
  "babel-preset-es2015": "^6.22.0",
  "babel-preset-react": "^6.22.0",
  "css-loader": "^0.26.1",
  "less": "^2.7.2",
  "less-loader": "^2.2.3",
  "react": "^15.4.2",
  "react-dom": "^15.4.2",
  "style-loader": "^0.13.1",
  "webpack": "^1.14.0",
  "webpack-dev-server": "^1.16.2"
}

JSX

简单说一下React的JSX语法是个神马东西
可能一会儿大家会看大不明觉厉的代码
比如

return (
    <div>hehe<div>
)

这就是JSX代码,它是React提供的语法糖
是React的重要组成部分,使用类似XML标记的方式来声明界面及关系
语法糖的意思我写ES6的时候也说了
就是计算机语言中添加的语法,对语言的功能没影响
方便我们开发人员使用的,可以增强可读性

如果使用JS代码也可以,不过官方推荐使用JSX
这样结构层次关系都很清晰
webpack会帮我们把他们转换成浏览器认识的js代码(loader的作用)
(如果好奇转换成了什么,可以去webpack输出文件查看,或者找jsx转js的工具)


JSX语法结构说的通俗一点就是HTML、JS混写
可能大家会有疑惑,说好的结构、样式、行为相分离的前端思想呢?!
React其中一个主要的设计理念是编写简单且容易理解的代码
但我们为了实现组件化确实不方便松耦合
大家也不要过分较真


这样的语法结构是怎样解析的呢?其实并不神奇
JSX的语法规则:

  • 遇到 HTML 标签(以 < 开头),就使用 HTML 规则解析
  • 遇到代码块(以 { 开头),就使用 JavaScript 规则解析
  • 代码块中如果只有一个数组变量,就展开这个数组的所有成员

不理解不要慌,看了下面就懂了
提前渗透一下

渲染到页面

终于写到语法正题了
在此之前我们必须要引用的两个对象
一个React核心对象和一个React-Dom对象
(这里就先不使用ES6的语法了)

var React = require('react');
var ReactDom = require('react-dom');

ReactDom.render()是react最最基本的方法
所以我放到最开始来讲
它通过ReactDom将我们的组件渲染到页面
我在页面中添加一个节点

<div id="root">div>

现在页面中什么也没有
不过马上就有了

ReactDom.render(
    <h1>Demoh1>,
    document.getElementById('demo')
);

第一个参数是要插入的组件(不过这里我们先插入一个DOM节点)
第二个参数就是要渲染的DOM节点
(React不建议直接添加到body标签document.body,不相信的话可以试一下会警告)

页面中出现了“Demo”

实际上react将我们的节点插入到了div节点的内部

组件化

React的一大特点就是组件化
React组件Component有以下特点

  • 组合:简单组件可组合为复杂组件
  • 重用:组件独立,可被多个组件使用
  • 测试:组件独立,便于测试
  • 维护:UI和组件相关逻辑都封装在组件内部,便于维护

组件生成

React.createClass()就是用于将代码封装成组件的方法
它会生成一个React组件

var App = React.createClass({
    render: function(){
        return (
            

This is a component...

) } });

这个方法参数是一个对象
对象中有一个render返回一个组件
render是输出组件必须要写的(关于它下面还会再说)
先记住两点

  • 组件名首字母一定大写,否则会报错
  • 输出的组件只能有一个顶级标签,其他标签会失效

所以,各位,下面的写法都是不对的

//错误的写法:变量名首字母没大写
var app = React.createClass({
    render: function(){
        return 

This is a component...

} })
//错误的写法:存在多个顶级标签
var App = React.createClass({
    render: function(){
        return (
            

This is a component...

This is also a component...

) } });

组件中html语法两边加括号的目的
是为了
防止JavaScript自动分号机制产生问题

组件渲染

生成的组件要想渲染到页面
就使用我们刚刚提到的的ReactDom.render( )

ReactDom.render(
    <App>App>,
    document.getElementById('root')
);

组件要写成标签的形式
这里我们就要写或者单标签形式也可以

组件特性

为了加以区分,我把html标签的属性叫组件特性

var App = React.createClass({
    render: function(){
        return 

"demo">This is a component...

; } }); ReactDom.render( , document.getElementById('root') );

还要注意两个特例

  • class要写成className
  • for要写成htmlFor

因为他们是JavaScript的保留字


如果想要为组件添加内联样式,可以这样写

var App = React.createClass({
    render: function(){
        var styles = {
            color: '#fff',
            backgroundColor: '#000'
        }
        return 

"demo" style={styles}>This is a component...

; // <-- } }); ReactDom.render( , document.getElementById('root') );

声明了一个styles对象
但是将它添加到属性时,要使用 { }
因为JSX语法中,html中使用js就必须使用大括号
关于这一点下面就不再赘述了

组件属性

this.props

组件的属性同样可以像html一样添加
并且这个组件属性内部可以通过this.props对象获取

var App = React.createClass({
    render: function(){
        return 

name:{this.props.name} age:{this.props.age}

; } }); ReactDom.render( "payen" age="20">, document.getElementById('root') );


了解了这个,我们可以做一个小练习
现在有一组数据,利用它组成一个有序列表组件

var data = ['Mr.A','Mr.B','Mr.C'];

可以将这个数组成为组件属性
然后利用this.props.data获取数据
最后使用ES5数组的map方法就大功告成了

var List = React.createClass({
    render: function(){
        return (
            
    { this.props.data.map(function(item, index){ return
  1. 1000 + index}>{item}
  2. ; }) }
) } }); ReactDom.render( <List data={data}>List>, document.getElementById('root') );

还要注意

  • {item}

  • key值如果不写的话,虽然可以正常渲染
    但会警告我们数组或迭代器的每一项都应该有一个独一无二的key值

    客户端高性能组件化框架React简介、特点、环境搭建及常用语法_第4张图片

    这里我就使用了1000加上索引的形式添加了key值

    this.props.children

    通常组件的属性与this.props对象中的属性是一一对应的
    但有一个例外,它是this.props.children
    它表示我们组件的所有子节点
    什么意思呢?接着我们上面的例子
    我们在List组件中添加一些子节点
    修改ReactDom.render( )方法的参数

    ReactDom.render(
        <List data={data}>
            <span>Mr.Dspan>
            <span>Mr.Espan>
        List>,
        document.getElementById('root')
    );

    我们发现页面中并没有什么变化,但浏览器也没有报错
    这时我们需要使用this.props.children

    var data = ['Mr.A','Mr.B','Mr.C'];
    var List = React.createClass({
        render: function(){
            console.log(this.props.children);
            return (
                
      { this.props.data.map(function(item, index){ return
    1. 1000 + index}>{item}
    2. ; }) } { this.props.children }
    ) } }); ReactDom.render( <List data={data}> Mr.D Mr.E List>, document.getElementById('root') );

    如此页面中就显示出了子节点

    客户端高性能组件化框架React简介、特点、环境搭建及常用语法_第5张图片

    这个this.props.children很奇怪,它有三种类型值

    • 没有子节点,值为undefined
    • 有一个子节点,值为object对象
    • 有多个子节点,值为array数组

    (可以在控制台上输出验证)
    所以我们处理它要特别小心
    好在我们可以使用React给我们提供的方法
    利用React.Children.map( )我们就可以放心遍历处理子节点

    var data = ['Mr.A','Mr.B','Mr.C'];
    var List = React.createClass({
        render: function(){
            return (
                
      { this.props.data.map(function(item, index){ return
    1. 1000 + index}>{item}
    2. ; }) } { React.Children.map(this.props.children,function(child){ return
    3. {child}
    4. }) }
    ) } }); ReactDom.render( <List data={data}> Mr.D Mr.E List>, document.getElementById('root') );

    客户端高性能组件化框架React简介、特点、环境搭建及常用语法_第6张图片
    客户端高性能组件化框架React简介、特点、环境搭建及常用语法_第7张图片

    组件验证

    组件的属性可以接受任何值,数字、字符串、函数、对象什么都可以
    但有时候,我们拿到一个组件,想要验证参数是否符合我们的要求(这其实很重要,不要轻视)
    这时就需要使用组件的propTypes( )方法和React.PropTypes配合验证了

    var data = ['Mr.A','Mr.B','Mr.C'];
    var App = React.createClass({
        propTypes: {
            data: React.PropTypes.array
        },
        render: function(){
            return (
                
    {this.props.data}
    ) } }); ReactDom.render( , document.getElementById('root') );

    这里我期望的data属性值为array数组类型
    没有任何问题,因为我们传入的就是数组
    可是如果改成期望字符串类型data: React.PropTypes.string

    浏览器就会发出警告

    详细见React中文官网:Prop 验证

    组件嵌套

    还记得React单向数据流的特点么
    也就是说我们应该把数据传递给父节点
    父节点通过this.prop将数据传递给子节点
    子节点再通过自己的this.prop处理收到的数据

    var data = ['Mr.A','Mr.B','Mr.C'];
    var List = React.createClass({
        render: function(){
            return (
                
      { this.props.data.map(function(item, index){ return
    1. 1000 + index}>{item}
    2. ; }) }
    ) } }); var App = React.createClass({ render: function(){ return (
    <List data={this.props.data}>List>
    ) } }); ReactDom.render( , document.getElementById('root') );

    所呈现的DOM结构

    客户端高性能组件化框架React简介、特点、环境搭建及常用语法_第8张图片

    生命周期

    生命周期不难理解
    组件的一生无非就是产生、更新、销毁
    在组件的每一个生命周期内,都会按顺序触发一些组件方法
    比如我们刚刚的render方法就会在产生和更新的阶段都会触发
    具体触发的回调函数API以及作用给大家整理在下面
    (关于它们在整个生命周期的触发次数大家应该都能想明白就不写了)
    (不常用的我在后面的标注了*号)

    • 组件实例化Mouting【组件生成时触发】
      • getDefaultProps( )
        • 作用于组件类,返回对象用于设置默认的this.props(引用值会在实例中共享)
        • e,g.return {name: 'payen'} 相当于初始化了组件属性this.props.name = 'payen'
      • getInitialState( )
        • 作用于组件的实例,返回对象作为this.state的初始值
        • e,g.return {show: false} 相当于初始化了组件状态this.state.show = false
      • componentWillMount( )
        • 首次渲染前调用,可做一些业务初始化操作,也可以通过this.setState()设置组件状态
        • e,g.this.setState({show: false})
      • render( )
        • 必选方法,用于创建虚拟DOM,有特殊规则(再啰嗦一遍)
          • 只能通过this.props和this.state访问数据
          • 可以返回null、false或任何React组件
          • 只能出现一个顶级组件(不能返回数组)
          • 不能改变组件的状态
          • 不能修改DOM的输出
      • componentDidMount( )(服务器端不会调用)
        • 真实DOM被渲染后调用,可通过this.getDOMNode()访问到真实的DOM元素
          此时可使用其他类库来操作该DOM
    • 组件存在期Updateing【组件更新时触发】(state,props变化触发)
      • componentWillReceiveProps( )*
        • 组件接收新props时调用,并将其作为参数nextProps使用,此时可以更改组件props及state
      • shouldComponentUpdate( )*
        • 组件是否应当渲染新props或state
          返回false表示跳过后续生命周期方法(通常不需要使用以避免出现bug)
          在出现应用瓶颈时,可通过该方法进行适当的优化。
          在首次渲染期间或者调用了forceUpdate方法后,该方法不会被调用
      • componentWillUpdate( )
        • 接收到新props或state后,进行渲染前调用,此时不允许更新props或state
      • render( )
        • 不再赘述
      • componentDidUpdate( )*
        • 完成渲染新的props或state后调用,此时可以访问到新的DOM元素
    • 组件销毁期Unmounting【组件销毁时触发】
      • componentWillUnmount()*
        • 组件移除前调用,可用于做一些清理工作
          在componentDidMount中添加的所有任务都需要在该方法中撤销(e.g.定时器、事件监听器等等)


    附上一张我盗的图,帮助大家理解(手动滑稽)

    客户端高性能组件化框架React简介、特点、环境搭建及常用语法_第9张图片

    关于这些API更详细的信息
    建议大家可以去React中文官网查看:Component Specs and Lifecycle

    组件状态

    上面提到了this.state,和我们之前介绍的this.props一样重要
    不过this.props通常不会变,但this.state会变
    就如其字面意思,表示组件的状态
    这个属性是只读的
    所以设置状态我们需要使用this.setState( )
    可使用this.setState( )的方法:
    componentWillMount、componentDidMount、componentWillReceiveProps

    组件交互

    在此之前我们需要了解的就是React的事件系统
    JavaScript原始行间绑定事件都是普遍小写
    但我们在React中要使用驼峰写法

    React的事件处理器会传入虚拟事件对象的实例(一个对浏览器本地事件的跨浏览器封装)
    它有和浏览器本地事件相同的属性和方法,包括 stopPropagation() 和 preventDefault(),
    但是没有浏览器兼容问题

    详细支持事件见中文官网:事件系统-支持的事件


    现在我们要来实现这样一个简单的功能
    点击按钮,出现弹框
    单击弹框,弹框消失

    先来实现结构与样式

    var App = React.createClass({
        render: function(){
            return (
                
    ) } }); var PopUp = React.createClass({ render: function(){ var styles = { position: 'absolute', left: '40px', top: '40px', width: '100px', height: '100px', backgroundColor: '#f40' } return (
    "popup" style={styles}>
    ) } }) ReactDom.render( , document.getElementById('root') );

    客户端高性能组件化框架React简介、特点、环境搭建及常用语法_第10张图片


    首先我们先来实现第一个功能:点击按钮出现弹框
    问题是改如何实现
    我们的React是单向数据流
    父级向子级传递数据
    最好的办法就是在父级设置组件状态this.state
    将状态通过组件属性this.props传递给子级
    这样点击事件要做的就是改变父级状态
    子级状态也会随之改变

    var App = React.createClass({
        getInitialState: function(){
            return {
                open: false
            }
        },
        buttonHandler: function(){
            this.setState({
                open: true
            });
        },
        render: function(){
            return (
                
    this.state.open}>
    ) } }); var PopUp = React.createClass({ render: function(){ var styles = { position: 'absolute', left: '40px', top: '40px', width: '100px', height: '100px', backgroundColor: '#f40' } if(this.props.open){ styles.display = 'block'; }else{ styles.display = 'none'; } return (
    "popup" style={styles}>
    ) } }) ReactDom.render( , document.getElementById('root') );

    第一个功能实现了,再来看第二个
    点击弹窗让其消失
    同样子级的显示与否掌控在父级手里
    要向让子级消失,就必须要改变父级的组件状态this.state
    所以我们必须要把事件函数绑定在父级
    再利用组件属性this.props将其传递给子级
    完整代码如下

    var App = React.createClass({
        getInitialState: function(){
            return {
                open: false
            }
        },
        buttonHandler: function(){
            this.setState({
                open: true
            });
        },
        popupHandler: function(){
            this.setState({
                open: false
            });
        },
        render: function(){
            return (
                
    this.state.open} handler={this.popupHandler}>
    ) } }); var PopUp = React.createClass({ render: function(){ var styles = { position: 'absolute', left: '40px', top: '40px', width: '100px', height: '100px', backgroundColor: '#f40' } if(this.props.open){ styles.display = 'block'; }else{ styles.display = 'none'; } return (
    "popup" style={styles} onClick={this.props.handler}>
    ) } }) ReactDom.render( , document.getElementById('root') );

    客户端高性能组件化框架React简介、特点、环境搭建及常用语法_第11张图片

    用一句话来总结一下,那就是数据都交给父级来管理

    获取真实DOM节点

    我们已经知道了
    创建的组件都是虚拟DOM节点
    只有当它渲染到了页面,才会成为真正的DOM节点
    但是有些时候,我们需要获取到真正的DOM节点
    这时需要先设置标签ref属性,再利用组件的this.refs对象获取

    还是通过一个小例子来解释
    现在要实现这样一个功能
    在输入栏中输入字符并在外部实时输出
    我们要获取的真实DOM节点就是input中的输入字符串
    步骤也很简单,完整代码如下

    var Input = React.createClass({
        getInitialState: function(){
            return {
                val: ''
            }
        },
        changeHandler: function(){
            this.setState({
                val: this.refs.node.value
            });
        },
        render: function(){
            return (
                
    "text" ref="node" onChange={this.changeHandler}/>

    {this.state.val}

    ) } }); ReactDom.render( , document.getElementById('root') );

    我为input标签设置了ref值为node
    可以把它理解为为这个节点起了个小名“node”
    那么this.refs.node就可以引用这个真实的节点
    通过绑定一个change事件
    我们的输入每次改变都会改变组件的状态state
    state改变,value就会渲染到页面

    获取真实DOM节点还有一个不常用的方法
    比如在我们的例子中可以把input标签改成这样

    "text" ref={function(dom){this._node = dom}.bind(this)} onChange={this.changeHandler}/>

    向ref属性中添加一个匿名函数
    这个函数的参数就是真实DOM节点
    我们可以把它保存下来,比如做为组件的_node属性
    不要忘了改变this的指向
    事件触发函数就可以通过this._node获取真正的DOM节点

    changeHandler: function(){
        this.setState({
            val: this._node.value
        });
    }

    还要注意的一点是
    这个真·DOM节点的获取
    必须要等到虚拟DOM插入文档以后,才能使用属性this.refs.[ref-name]
    否则会报错的


    ==主页传送门==

    你可能感兴趣的:(Web前端,React)