2

React 入门与实战

react组件

虚拟DOM的概念 这是React性能高效的核心算法

React为此引入了虚拟DOMVirtual DOM)的机制。基于React进行开发时所有的DOM构造都是通过虚拟DOM进行,每当数据变化时,React都会重新构建整个DOM树,然后React将当前整个DOM树和上一次的DOM树进行对比,得到DOM结构的区别,然后仅仅将需要变化的部分进行实际的浏览器DOM更新。尽管每一次都需要构造完整的虚拟DOM树,但是因为虚拟DOM是内存数据,性能是极高的,而对实际DOM进行操作的仅仅是Diff部分,因而能达到提高性能的目的。


React 组件,理解什么是组件化

像插入普通 HTML 标签一样,在网页中插入这个组件

所谓组件,即封装起来的具有独立功能的UI部件

  • React推荐以组件的方式去重新思考UI构成,将UI上每一个功能相对独立的模块定义成组件,然后将小的组件通过组合或者嵌套的方式构成大的组件,最终完成整体UI的构建。
  • 对于React而言,则完全是一个新的思路,开发者从功能的角度出发,将UI分成不同的组件,每个组件都独立封装。

React一个组件应该具有如下特征:

  1. 可组合(Composeable):一个组件易于和其它组件一起使用,或者嵌套在另一个组件内部。如果一个组件内部创建了另一个组件,那么说父组件拥有它创建的子组件,通过这个特性,一个复杂的UI可以拆分成多个简单的UI组件.
  2. 可重用(Reusable):每个组件都是具有独立功能的,它可以被使用在多个UI场景
  3. 可维护(Maintainable):每个小的组件仅仅包含自身的逻辑,更容易被理解和维护

语法

  • 组件名称一定要大写
  • 组件只能包含一个顶层标签
// Header组件 
// export 导出供外部使用
export default class ComponentHeader extends React.Component{
    //render方法:用于解析本身类的输出
    render(){
        return (<header><h1>这里是头部</h1></header>)
    }
}



// Index组件
import  ComponentHeader from './component/header';
class Index extends React.Component{
    render(){
        // 插入普通 HTML 标签一样
        return <ComponentHeader/>
    }
}

// 将模版转化为HTML语言,并插入到指定节点
// 相当于程序的入口

ReactDOM.render(<Index/>,document.getElementById('one')) 

React 多组件嵌套

各个组件之间相互独立,利于维护,重用。在Index文件中进行调用,简明。
需要变更时,我们只需要改变相应的组件,所有引用该组件的的页面就都发生的变更,很好维护。

import  ComponentHeader from './component/header';
import  BodyIndex from './component/body';
import  ComponentFooter from './component/footer';

class Index extends React.Component{
    render(){
        //将组件赋值给一个变量,这样方便做判断
        var component;
        if(/*条件*/){
             component = <ComponentLoginedHeader/>;
        }else{
             component = <ComponentHeader/>;
        }
        
        return (
            <div>
                { component}
                <BodyIndex/>
                <ComponentFooter/>
            </div>
        )
    }
}

JXS内置表达式

  • HTML 语言直接写在 JavaScript 语言之中,不加任何引号,这就是 JSX 的语法,它允许 HTMLJavaScript 的混写。
  • JSX 的基本语法规则:遇到 HTML 标签(以 < 开头),就用 HTML 规则解析;遇到代码块(以 { 开头),就用 JavaScript 规则解析
  • JSX 中的注释 {/*注释*/}
export default class BodyIndex extends React.Component{
   render(){
   
    var userName = 'xiaoxiong';
    var boolInput;
    var html1 = 'Mooc&nbsp;Lesson';
    //对 &nbsp; 进行Unicode转码
    var html2 = 'Mooc\u0020Lesson'
       return (
           <div>
               <h2>页面主体内容</h2>
                // 三元表达式的使用
               <p>{userName = '' ?'用户还没有登录':'用户名:' +userName }</p>
               // 使用{boolInput}进行值的绑定
               <input type='button' value='按钮' disable={boolInput}>
               //解析html
               <p>{html1}</p>  //'Mooc&nbsp;Lesson' &nbsp;不会被解析为空格
               <p>{html2}</p>  //'Mooc Lesson'
               <p dangerouslySetInnerHTML={{__html:html1}}></p> //'Mooc Lesson' 此方法可能会存在xss攻击 
               
           </div>
       )
   }
}

声明周期,纵观整个React的生命周期

在ES6中,一个React组件是用一个class来表示的

过程描述

React中有4中途径可以触发render

  • 首次渲染Initial Render
constructor --> componentWillMount -->  render -->conponentDidMount 
  • 调用this.setState (并不是一次setState会触发一次renderReact可能会合并操作,再一次性进行render
shouldComponentUpdate --> componentWillUpdate --> render --> componentDidUpdate
  • 父组件发生更新(一般就是props发生改变,但是就算props没有改变或者父子组件之间没有数据交换也会触发render
componentWillReceiveProps --> shouldComponentUpdate --> componentWillUpdate --> render --> componentDidUpdate
  • 调用this.forceUpdate
componentWillUpdate --> render --> componentDidUpdate

总结

  • constructorcomponentWillMountcomponentDidMount只有第一次渲染时候会被调用
  • componentWillUpdatecomponentDidUpdateshouldComponentUpdate 在以后的每次更新渲染之后都会被调用

图片描述
图片描述
图片描述


考虑到性能的问题,如果有些属性的变化,不需要重新刷新页面,我们是使用 componentShouldUpdate() 进行控制。


官方文档
https://facebook.github.io/re...

参考文献
http://www.jianshu.com/p/4784...


react属性与事件

state 属性控制React的一切

组件自身的状态,props为外部传入的状态

state对组件做了更新之后,会马上反应到虚拟DOM上,最后更新到DOM上。这个过程是自动完成的。

图片描述

组件免不了要与用户互动,React 的一大创新,就是将组件看成是一个状态机,一开始有一个初始状态,然后用户互动,导致状态变化,从而触发重新渲染 UI .

export default class BodyIndex extends React.Component{
   // 初始化
   constructor(){
       super();//调用基类的所有初始化方法
       //state的作用域是当前组件,不会污染其他模块
       this.state = {
           username:"xiaoxiong"
       };
       
   };
   
   //修改state
   setTimeOut(()=>{
       this.setState({username:"miaomiao"})
   },4000);
   
   render(){
       return (
           <div>
               <h2>页面主体内容</h2>
               // 引用state值
               <p>{this.state.username}</p>
           </div>
       )
   }
}

Props属性

其他组件传递参数,对于本模块来说,属于外来属性。

图片描述

//使用组件时,传入参数
< BodyIndex userid="123" username={xiaoxiong}/>


export default class BodyIndex extends React.Component{
  
    render(){
        return (
            <div>
                <h2>页面主体内容</h2>
                // 接收参数
                <p>{this.props.userid}</p>
                <p>{this.props.username}</p>
            </div>
        )
    }
}

添加组件属性,有一个地方需要注意,就是 class 属性需要写成 classNamefor 属性需要写成 htmlFor ,这是因为 classforJavaScript 的保留字。


事件和数据的双向绑定,包含了父子页面之间的参数互传

  • 父组件给子组件传递参数,使用props

事件绑定

export default class BodyIndex extends React.Component{
    constructor(){
        super();
        this.state={
            username="xiaoxiong",
            age:20
        }
    };
    
    changeUserInfo(){
        this.setState({age:50})
    };
  
    render(){
        return (
            <div>
                <h2>页面主体内容</h2>
                <p>{this.props.username} {this.props.age }</p>
                //事件绑定 ES6写法 
                //ES5写法 onClick=this.chanchangeUserInfo
                <input type="button" value="提交" onClick=this.chanchangeUserInfo.bind(this)/>
            </div>
        )
    }
}
  • 子组件为父组件传参使用事件
  • 在子页面中通过调用父页面传递过来的事件 props 进行组件间的参数传递

就是让子页面的变动体现在父页面上,而页面状态的改变由state控制,因此我们让父页面通过props将函数传递给子页面,此函数可以取得子页面的值,并改变父页面的state

//子组件
export default class BodyChild extends React.Component{
         
    render(){
        return (
            <div>
                // 调用父组件传过来的函数
                <p>子页面输入:<input type="text" onChange={this.props.handler}/></p>
            </div>
        )
    }
}

//父组件
import BodyChild from "./component/bodychild";

export default class Body extends React.Component{

    constructor(){
        super();
        this.state={
            age:20
        }
    }

    // 父页面的函数,可以操控父页面的 state
    handler(e){
        this.setState({age:e.target.value})
    }
   
    render(){
        return (
            <div>
                <p>{this.state.age}<p>
                <BodyChild handler={this.handler.bind(this)}/>
            </div>
        )
    }
}

可复用组件,真正让React开发快速、高效的地方

使用组件时,传递props,在组件定义的文件中可使用这些props

export default class BodyIndex extends React.Component{
   
    render(){
        return (
            <div>
                <p>{this.props.userid}<p>
                <p>{this.props.username}<p>
            </div>
        )
    }
}

// 对传递过来的 props 的类型进行约束
BodyIndex.propTypes = {
    // userid为number类型且必须传递
    userid:React.propTypes.number.isRuquired
};

// 设置默认值,使用组件时,如果不传递参数,将显示默认值
BodyIndex.defaultProps = {
    username:"xiaoxiong"
};

组件多层嵌套时,传递参数的简便方法

export default class Body extends React.Component{
   
    render(){
        return (
            <div>
                //在父页面定义props
               <BodySon username="xiaoxiong" age="25"/>
            </div>
        )
    }
}


export default class BodySon extends React.Component{
   
    render(){
        return (
            <div>
                <p>{this.props.username}</p>
                //取得父页面(BodySon)的所有props属性
               <Bodygrandson ...this.props />
            </div>
        )
    }
}



export default class Bodygrandson extends React.Component{
   
    render(){
        return (
            <div>
               // 使用传递过来的props
               <p>{this.props.username}{this.props.age}</p>
            </div>
        )
    }
}


组件的Refs
React中多数情况下,是通过state的变化,来重新刷新页面。但有时也需要取得html节点,比如对input进行focus等;下面我们来看下,怎么取得原生的html节点,并对其进行操作。

export default class BodyChild extends React.Component{

    handler(){
        //第一种方式
        var mySubmitBotton = document.getElementById("submitButton");
        //这样也是可以的
        mySubmitBotton.style.color="red";
        ReactDOM.findDOMNode(mySubmitBotton).style.color = 'red';
        
        //第二种方式
        this.refs.submitBotton.style.color = 'red';
        
    }
         
    render(){
        return (
            <div>
                <input id="submitButton" ref="submitButton"type="button" onClick={this.handler.bind(this)}/>
            </div>
        )
    }
}
  • refs是访问到组件内部DOM节点的唯一可靠方式
  • refs会自动销毁对子组件的引用
  • 不要在RenderRender之前对Refs进行调用,因为Refs获取的是真实的DOM节点,要在插入真实DOM节点之后调用。
  • 不要滥用Refs
  • 不必须使用真实DOM时,用方法1即可

独立组件间共享Mixins

在所有的组件见共享一些方法

ES6中使用mixin需要插件支持

npm install react-mixin --save

使用

//mixin.js
const MixinLog = {
    //有自身的生命周期
    componentDidMount(){
        console.log('MixinLog ComponentDidMount');
    },
    log(){
        console.log('Mixins');
    }
};

//暴露供外部使用
export default MixinLog;

//body.js
//导入MixinLog 
import MixinLog from './component/mixin.js';
import ReactMixin from 'react-mixin';
export default class Body extends React.Component{

     handler(){
        //调用MixinLog中方法
        MixinLog.log();
     }
   
    render(){
        return (
            <div>
               <p>{this.props.username}</p>
            </div>
        )
    }
}

ReactMixin(Body.prototype,MixinLog);

react样式

内联样式

原生中用 - 连接的样式属性,在这里要采用驼峰写法或加引号"",属性值一律加引号"" ,这样的书写方式,实际上就是加入了内联样式,结构和样式混在一起,不是很好的做法。并且这种写法,不能使用伪类、动画。

export default class Header extends React.Component{
    
    render(){
    //将样式定义为一个变量
    //注意样式属性的书写
    const styleComponentHeader = {
        header:{
            backgroundColor:"#333",
            color:"#fff",
            "padding-top":"15px",
            paddingBottom:"15px",
        },
        //还可以定义其他样式
    };

        return (
            //使用样式,这样写,实际上就是内联样式
            <header style={styleComponentHeader. header}>
                <h1>头部</h1>
            </header>
        )
    }
}

也可以在index.html文件中,引入css文件,并在需要使用样式的地方加入类名(className),但这种写法会污染全局。

//index.html
<header>
    <link href="../css.js">
</header>
<div id="one"></div>

//组件
export default class Header extends React.Component{
    
    render(){
        return (
            //使用类名,加入样式
            <header className="header">
                <h1>头部</h1>
            </header>
        )
    }
}

内联样式中的表达式

根据state的值,控制样式

export default class Header extends React.Component{
    
    constructor(){
        super();
        this.state={
            miniHeader:false
        }
    }
    //点击头部,样式发生变化
    swithHeader(){
        this.setState({
           miniHeader:!this.state.miniHeader
        })
    }
    
    render(){
    const styleComponentHeader = {
        header:{
            backgroundColor:"#333",
            color:"#fff",
            "padding-top":"15px",
            //根据state的值,变化 paddingBottom的值
            //样式中可以使用表达式
            paddingBottom: (this.state.miniHeader)?"3px":"15px"
        },
    };

        return (
        
            <header style={styleComponentHeader. header} onClick={this.swithHeader.bind(this)}>
                <h1>头部</h1>
            </header>
        )
    }
}

CSS模块化,学习如何使用require进行样式的引用
问题:

  • 怎样实现css的按需加载,使用这个模块时,只加载这个模块的样式。
  • 模块之间的样式是相互独立的,即使使用相同的类名,也不会相互影响。

-

使用webpack,安装相关插件

//使用require导入css文件
npm install css-loader --save
//计算之后的样式加入页面中
npm install style-loader --save

webpack.config.js文件中进行配置
css文件导入相应的组件后,会生成一个对应关系

footer:_footer_minifooter_vxs08s

_footer_minifooter_vxs08s是按照一定的规则生成一个唯一的类名,当我们为当前组件应用 .footer 类的时候,就会按照这个对应关系去样式文件中找,然后应用响应的样式。
实际上是改变了样式文件中类的名称,使其唯一。
这样即使将所有文件打包到一起,也不会引起冲突。
不同的文件可以 使用相同的类名,不会冲突,因为模块化之后,类名都进行了转换。

module:{
        loaders:[
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
                query: {
                    presets: ['es2015','react']
                }
            },
            {
                test: /\.css$/,
                //modules 模块化配置
                loader: 'style-loader!css-loader?modules'
            },
        ]
    },

使用

//footer.css

.footer{
    background-color:#333;
}


//Footer组件
//导入css样式,这样这个样式文件只作用于这个组件
var  footerCss = require('./css/footer.css');

export default class Footer extends React.Component{
    
    render(){
        return (
            <footer className="footerCss.footer">
                <h1>底部</h1>
            </footer>
        )
    }
}

总结

为什么要css模块化

  • 可以避免全局污染、命名混乱

模块化的优点

  • 所有的样式都是local的,解决的命名冲突和全局污染的问题
  • class名生成规则配置灵活,可以以此来压缩class名(在webpack.config.js文件中配置)
  • 使用某个组件时,只要import组件,再使用即可,无需管理样式。
  • 不需要像书写内部样式一样,属性的名称需要驼峰写法。只需在webpack.config.js中进行配置,书写时,还是我们熟悉的css

JSX样式与CSS样式互转

线上转换工具
http://staxmanade.com/CssToRe...


react-router

Router 概念


控制页面之间的层级关系
底层机制
通过状态的改变,导致组件从新渲染,从而改变页面显示

React: state/props -> Component ->UI

通过改变url,导致Router变化,从而改变页面显示

React:location(hasj) -> Router ->UI

hashHistory && browserHistory

慕课老师的demo使用的是hashHistory,而另一种方式则是使用browserHistory

如果希望使用browserHistory达到hashHistory的效果,则需要做2件事情:

1、服务器支持。如果开发服务器使用的是webpack-dev-server,加上--history-api-fallback参数就可以了。
2、作为项目入口的HTML文件中,相关外链的资源路径都要改为绝对路径,即以"/"根路径开头。


安装

// 版本 2.8.1
npm install react-router

使用

component指定组件
path 指定路由的匹配规则

router可以进行嵌套
ComponentDetails嵌套在Index页面中,我们要在Index中进行展示。

//index.js
export default class Index extends React.Component{

    render(){
        return ( 
            <div>
                //此处展示的是ComponentDetails页面
                {this.props.children} 
            </div>
        )
    }
}
import React from 'react';
import ReactDOM from 'react-dom';
import Index from './index';
import { Router, Route, hashHistory} from 'react-router';
import ComponentList from './components/list';
import ComponentHeader from './components/header';
import ComponentDetails from './components/details';

export  default class Root extends React.Component{
    render(){
        //这里替换了之前的index,变成了程序的入口 (注意修改webpack.conf.js中的入口文件为root.js)
        return (
            <Router history={hashHistory}>
                <Route component={Index} path="/">
                    <Route component={ComponentDetails} path="details"></Route>
                </Route>
                <Route component={ComponentList} path="list/:id"></Route>
                <Route component={ComponentHeader} path="header"></Route>
            </Router> 
        );
    };
}


ReactDOM.render(
    <Root/>,
    document.getElementById('example')
);

有了Router之后,用Link进行跳转

<Link to={`/`}>首页</Link>
<Link to={`/details`}>嵌套的详情页面</Link>

Router 参数传递

//在Router中定义参数名称
<Route component={ComponentList} path="list/:id"></Route>

//在Link中传入参数
<Link to={`/list`}></Link>

//在list组件页面中读取传入的参数
render(){
    <div>{this.props.params.id}</div>
}

使用NPM配置React环境

//初始化  建立初始化文件
npm init

package.json文件

npmstart是一个特殊的脚本名称,它的特殊性表现在,在命令行中使用npm start就可以执行相关命令,如果对应的此脚本名称不是start,想要在命令行中运行时,需要这样用npm run {script name}npm run build

{
  "name": "reactconf",//项目名称
  "version": "1.0.0",//项目版本
  "description": "",//项目描述
  "main": "root.js",//入口文件
  //自定义的脚本任务
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server --inline --content-base ."
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "antd": "^2.10.1",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "babelify": "^7.3.0",
    "css-loader": "^0.25.0",
    "react": "^15.5.4",
    "react-dom": "^15.5.4",
    "react-mixin": "^2.0.2",
    "react-router": "^2.8.1",
    "style-loader": "^0.13.1",
    "webpack": "^1.13.2",
    "webpack-dev-server": "^1.16.1"
  },
  "devDependencies": {
    "babel-core": "^6.24.1",
    "babel-loader": "^7.0.0"
  }
}

安装依赖

babelify
Babel其实是一个编译JavaScript的平台,它的强大之处表现在可以通过编译帮你达到以下目的:

  • 下一代的JavaScript标准(ES6ES7),这些标准目前并未被当前的浏览器完全的支持
  • 使用基于JavaScript进行了拓展的语言,比如ReactJSX

babel-preset-react react转码规则
babel-preset-es2015 ES2015转码规则


npm install react react-dom babelify --save

npm install  babel-preset-react babel-preset-es2015 --save

使用webpack打包

安装相关的包

npm install webpack -g 
npm install webpack-dev-server -g
npm install webpack  --save
npm install webpack-dev-server --save
//webpack.config.js

// 引用webpack相关的包
var webpack = require('webpack');
var path = require('path');
var WebpackDevServer = require("webpack-dev-server");

module.exports = {
    //入口文件  __dirname 项目根目录
    //“__dirname”是node.js中的一个全局变量,它指向当前执行脚本所在的目录
    context:  __dirname + "/src",
    entry:"./js/root.js",
    
    //loaders
    module:{
        loaders:[
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel',
                query: {
                    presets: ['es2015','react']
                }
            },
            //下面是使用 ant-design 的配置文件 不再使用 ?modules 因为样式是全局的 不再需要局部样式
            {
                test: /\.css$/,
                //modules  模块化配置
                loader: 'style-loader!css-loader?modules'
            },
        ]
    },
    
    //出口文件
    output: {
        path:   __dirname + '/src/',
        filename: 'bundle.js'
    },
};

打包命令

// 正常情况下
webpack

//配置热加载情况下
webpack-dev-server --inline

使用 --watch 可是自动打包,但不会自动刷新

可以用content-base设定 webpack-dev-server 伺服的 directory (就是指管这个路径下文件的变动),如果不进行设定的话,默认是在当前目录下

webpack-dev-server --content-base -src --inline

关于webpack

为什么使用webpack

现今的很多网页其实可以看做是功能丰富的应用,它们拥有着复杂的JavaScript代码和一大堆依赖包。为了简化开发的复杂度,前端社区涌现出了很多好的实践方法

  • 模块化,让我们可以把复杂的程序细化为小的文件;类似于TypeScript这种在JavaScript基础上拓展的开发语言,使我们能够实现目前版本的JavaScript不能直接使用的特性,并且之后还能能装换为JavaScript文件使浏览器可以识别.
  • ScsslessCSS预处理器
  • ...

这些改进确实大大的提高了我们的开发效率,但是利用它们开发的文件往往需要进行额外的处理才能让浏览器识别,而手动处理又是非常繁琐的,这就为WebPack类的工具的出现提供了需求。


webpack并不强制你使用某种模块化方案,而是通过兼容所有模块化方案让你无痛接入项目,当然这也是webpack牛逼的地方


webpackgulp的区别

gulp是工具链、构建工具,可以配合各种插件做js压缩,css压缩,less编译 替代手工实现自动化工作

1.构建工具

2.自动化

3.提高效率用

webpack是文件打包工具,可以把项目的各种js文、css文件等打包合并成一个或多个文件,主要用于模块化方案,预编译模块的方案

1.打包工具

2.模块化识别

3.编译模块代码方案
  • 虽然都是前端自动化构建工具,但看他们的定位就知道不是对等的。
  • gulp严格上讲,模块化不是他强调的东西,他旨在规范前端开发流程。
  • webpack更是明显强调模块化开发,而那些文件压缩合并、预处理等功能,不过是他附带的功能。

webpack工作方式

Webpack的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个浏览器可识别的JavaScript文件。


Loaders

通过使用不同的loaderwebpack通过调用外部的脚本或工具可以对各种各样的格式的文件进行处理,比如说分析JSON文件并把它转换为JavaScript文件,或者说把下一代的JS文件(ES6ES7)转换为现代浏览器可以识别的JS文件。或者说对React的开发而言,合适的Loaders可以把ReactJSX文件转换为JS文件。

Loaders需要单独安装并且需要在webpack.config.js下的modules关键字下进行配置,Loaders的配置选项包括以下几方面:

test:一个匹配loaders所处理的文件的拓展名的正则表达式(必须)
loaderloader的名称(必须)
include/exclude:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选);
query:为loaders提供额外的设置选项(可选)


依赖模块

// MediaQuery 进行移动端适配
var MediaQuery = require('react-responsive');
<MediaQuery query='(min-device-width:1224px)'>
fetch 向后台请求数据

响应式 以1224px为分界



小熊苗苗
328 声望18 粉丝

如果你的才华还实现不了你的野心,那就静下心来,埋头苦干