关于React全家桶的介绍

起步

首先需要安装react: npm install react react-dom --save-dev

JSX 语法

JSX 的基本语法规则:遇到 HTML 标签(以 < 开头),就用 HTML 规则解析;遇到代码块(以 { 开头),就用 JavaScript 规则解析。JSX 允许直接在模板插入 JavaScript 变量。如果这个变量是一个数组,则会展开这个数组的所有成员

var arr = [
    

...

,

...

, ]; ReactDOM.render(
{arr}
, document.getElementById('example') );

组件

ReactDOM.render(
    ,
    document.getElementById('example')
);

变量 HelloMessage 就是一个组件类。模板插入时,会自动生成 HelloMessage 的一个实例(下文的"组件"都指组件类的实例)。所有组件类都必须有自己的 render 方法,用于输出组件。

注意,组件类的第一个字母必须大写,否则会报错,比如HelloMessage不能写成helloMessage。另外,组件类只能包含一个顶层标签,否则也会报错。

上面代码会报错,因为HelloMessage组件包含了两个顶层标签

组件的用法与原生的 HTML 标签完全一致,可以任意加入属性,比如,就是 HelloMessage 组件加入一个 name 属性,值为 John。组件的属性可以在组件类的this.props对象上获取,比如 name 属性就可以通过this.props.name读取。

有一个地方需要注意,就是 class 属性需要写成 className ,for 属性需要写成 htmlFor ,这是因为 class 和 for 是 JavaScript 的保留字。

this.props.children

this.props 对象的属性与组件的属性一一对应,但是有一个例外,就是 this.props.children 属性。它表示组件的所有子节点

render: function() {
    return (
        
    { React.Children.map(this.props.children, function (child) { return
  1. {child}
  2. ; }) }
); } 上面代码的 NoteList 组件有两个 span 子节点,它们都可以通过 this.props.children 读取

这里需要注意, this.props.children 的值有三种可能:如果当前组件没有子节点,它就是 undefined ;如果有一个子节点,数据类型是 object ;如果有多个子节点,数据类型就是 array 。所以,处理 this.props.children 的时候要小心。

React 提供一个工具方法 React.Children 来处理 this.props.children 。我们可以用 React.Children.map 来遍历子节点,而不用担心 this.props.children 的数据类型是 undefined 还是 object。

获取真实的DOM节点

组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫做虚拟 DOM (virtual DOM)。只有当它插入文档以后,才会变成真实的 DOM 。根据 React 的设计,所有的 DOM 变动,都先在虚拟 DOM 上发生,然后再将实际发生变动的部分,反映在真实 DOM上,这种算法叫做 DOM diff ,它可以极大提高网页的性能表现。

但是,有时需要从组件获取真实 DOM 的节点,这时就要用到 ref 属性

var MyComponent = React.createClass({
handleClick: function() {
    this.refs.myTextInput.focus();
},
render: function() {
    return (
    
); } });

上面代码中,组件 MyComponent 的子节点有一个文本输入框,用于获取用户的输入。这时就必须获取真实的 DOM 节点,虚拟 DOM 是拿不到用户输入的。为了做到这一点,文本输入框必须有一个 ref 属性,然后this.refs.[refName]就会返回这个真实的 DOM 节点。

需要注意的是,由于this.refs.[refName]属性获取的是真实 DOM ,所以必须等到虚拟 DOM 插入文档以后,才能使用这个属性,否则会报错。上面代码中,通过为组件指定 Click 事件的回调函数,确保了只有等到真实 DOM 发生 Click 事件之后,才会读取this.refs.[refName]属性。

React 组件支持很多事件,除了 Click 事件以外,还有 KeyDown 、Copy、Scroll 等…

this.state

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

var LikeButton = React.createClass({
getInitialState: function() {
    return {liked: false};
},
handleClick: function(event) {
    this.setState({liked: !this.state.liked});
},
render: function() {
    var text = this.state.liked ? 'like' : 'haven\'t liked';
    return (
        

You {text} this. Click to toggle.

); } }); ReactDOM.render( , document.getElementById('example') );

上面代码是一个 LikeButton 组件,它的 getInitialState 方法用于定义初始状态,也就是一个对象,这个对象可以通过 this.state 属性读取。当用户点击组件,导致状态变化,this.setState 方法就修改状态值,每次修改以后,自动调用 this.render 方法,再次渲染组件。

由于 this.props 和 this.state 都用于描述组件的特性,可能会产生混淆。一个简单的区分方法是,this.props 表示那些一旦定义,就不再改变的特性,而 this.state 是会随着用户互动而产生变化的特性。

表单

用户在表单填入的内容,属于用户跟组件的互动,所以不能用 this.props 读取

var Input = React.createClass({
getInitialState: function() {
    return {value: 'Hello!'};
},
handleChange: function(event) {
    this.setState({value: event.target.value});
},
render: function () {
    var value = this.state.value;
    return (
        

{value}

); } }); ReactDOM.render(, document.body);

上面代码中,文本输入框的值,不能用 this.props.value 读取,而要定义一个 onChange 事件的回调函数,通过 event.target.value 读取用户输入的值。textarea 元素、select元素、radio元素都属于这种情况,更多介绍请参考官方文档。

组件的生命周期

组件的生命周期分成三个状态:

Mounting:已插入真实 DOM
Updating:正在被重新渲染
Unmounting:已移出真实 DOM

React 为每个状态都提供了两种处理函数,will 函数在进入状态之前调用,did 函数在进入状态之后调用,三种状态共计五种处理函数。

componentWillMount()
componentDidMount()
componentWillUpdate(object nextProps, object nextState)
componentDidUpdate(object prevProps, object prevState)
componentWillUnmount()

此外,React 还提供两种特殊状态的处理函数。

componentWillReceiveProps(object nextProps):已加载组件收到新的参数时调用
shouldComponentUpdate(object nextProps, object nextState):组件判断是否重新渲染时调用
这些方法的详细说明,可以参考官方文档。下面是一个例子(查看 demo10 )。

var Hello = React.createClass({
getInitialState: function () {
    return {
        opacity: 1.0
    };
},

componentDidMount: function () {
    this.timer = setInterval(function () {
        var opacity = this.state.opacity;
        opacity -= .05;
        if (opacity < 0.1) {
            opacity = 1.0;
        }
        this.setState({
            opacity: opacity
        });
    }.bind(this), 100);
},

render: function () {
    return (
    
Hello {this.props.name}
); } }); ReactDOM.render( , document.body );

上面代码在hello组件加载以后,通过 componentDidMount 方法设置一个定时器,每隔100毫秒,就重新设置组件的透明度,从而引发重新渲染。

另外,组件的style属性的设置方式也值得注意,不能写成style="opacity:{this.state.opacity};"而要写成style={{opacity: this.state.opacity}}这是因为 React 组件样式是一个对象,所以第一重大括号表示这是 JavaScript 语法,第二重大括号表示样式对象。

Ajax

组件的数据来源,通常是通过 Ajax 请求从服务器获取,可以使用 componentDidMount 方法设置 Ajax 请求,等到请求成功,再用 this.setState 方法重新渲染 UI

var UserGist = React.createClass({
getInitialState: function() {
    return {
    username: '',
    lastGistUrl: ''
    };
},

componentDidMount: function() {
    $.get(this.props.source, function(result) {
    var lastGist = result[0];
    if (this.isMounted()) {
        this.setState({
        username: lastGist.owner.login,
        lastGistUrl: lastGist.html_url
        });
    }
    }.bind(this));
},

render: function() {
    return (
    
{this.state.username}'s last gist is here.
); } }); ReactDOM.render( , document.body );

上面代码使用 jQuery 完成 Ajax 请求,这是为了便于说明。React 本身没有任何依赖,完全可以不用jQuery,而使用其他库。

我们甚至可以把一个Promise对象传入组件,请看Demo12。

ReactDOM.render(
promise={$.getJSON(‘https://api.github.com/search/repositories?q=javascript&sort=stars’)}
/>,
document.body
);
上面代码从Github的API抓取数据,然后将Promise对象作为属性,传给RepoList组件。

路由库React-Router

它通过管理 URL,实现组件的切换和状态的变化,开发复杂的应用几乎肯定会用到。
使用时,路由器Router就是React的一个组件。

import { Router } from 'react-router';
render(, document.getElementById('app'));

Router组件本身只是一个容器,真正的路由要通过Route组件定义。

import { Router, Route, hashHistory } from 'react-router';

render((

    

), document.getElementById('app'));

上面代码中,用户访问根路由/(比如http://www.example.com/),组件APP就会加载到document.getElementById(‘app’)。
你可能还注意到,Router组件有一个参数history,它的值hashHistory表示,路由的切换由URL的hash变化决定,即URL的#部分发生变化。举例来说,用户访问http://www.example.com/,实际会看到的是http://www.example.com/#/

Route组件定义了URL路径与组件的对应关系。你可以同时使用多个Route组件。





上面代码中,用户访问/repos(比如http://localhost:8080/#/repos)时,加载Repos组件;访问/about(http://localhost:8080/#/about)时,加载About组件。

Route组件还可以嵌套。



    
    


上面代码中,用户访问/repos时,会先加载App组件,然后在它的内部再加载Repos组件。

path属性可以使用通配符。


// 匹配 /hello/michael
// 匹配 /hello/ryan

// 匹配 /hello
// 匹配 /hello/michael
// 匹配 /hello/ryan

// 匹配 /files/hello.jpg
// 匹配 /files/hello.html

// 匹配 /files/ 
// 匹配 /files/a
// 匹配 /files/a/b

// 匹配 /files/hello.jpg
// 匹配 /files/path/to/file.jpg

通配符的规则如下。

(1):paramName
:paramName匹配URL的一个部分,直到遇到下一个/、?、#为止。这个路径参数可以通过this.props.params.paramName取出。

(2)()
()表示URL的这个部分是可选的。

(3)*
*匹配任意字符,直到模式里面的下一个字符为止。匹配方式是非贪婪模式。

(4) **
** 匹配任意字符,直到下一个/、?、#为止。匹配方式是贪婪模式。

Redirect 组件

组件用于路由的跳转,即用户访问一个路由,会自动跳转到另一个路由。


{/* 从 /inbox/messages/:id 跳转到 /messages/:id */}
<Redirect from="messages/:id" to="/messages/:id" />

现在访问/inbox/messages/5,会自动跳转到/messages/5。

Link

Link组件用于取代元素,生成一个链接,允许用户点击后跳转到另一个路由。它基本上就是元素的React 版本,可以接收Router的状态。

render() {
return 
  • About
  • Repos
}

如果希望当前的路由与其他路由有不同样式,这时可以使用Link组件的activeStyle属性。

About
Repos

上面代码中,当前页面的链接会红色显示。

另一种做法是,使用activeClassName指定当前路由的Class。

About
Repos

上面代码中,当前页面的链接的class会包含active。

在Router组件之外,导航到路由页面,可以使用浏览器的History API,像下面这样写。

import { browserHistory } from 'react-router';
browserHistory.push('/some/path');

如果链接到根路由/,不要使用Link组件,而要使用IndexLink组件。

histroy 属性

Router组件的history属性,用来监听浏览器地址栏的变化,并将URL解析成一个地址对象,供 React Router 匹配。

history属性,一共可以设置三种值。

browserHistory
hashHistory
createMemoryHistory

如果设为hashHistory,路由将通过URL的hash部分(#)切换,URL的形式类似example.com/#/some/path。

如果设为hashHistory,路由将通过URL的hash部分(#)切换,URL的形式类似example.com/#/some/path。

如果设为browserHistory,浏览器的路由就不再通过Hash完成了,而显示正常的路径example.com/some/path,背后调用的是浏览器的History API。但是,这种情况需要对服务器改造。否则用户直接向服务器请求某个子路由,会显示网页找不到的404错误。

createMemoryHistory主要用于服务器渲染。它创建一个内存中的history对象,不与浏览器URL互动。

表单处理

Link组件用于正常的用户点击跳转,但是有时还需要表单跳转、点击按钮跳转等操作。这些情况怎么跟React Router对接呢?

下面是一个表单。

    

使用context对象。

export default React.createClass({
    // ask for `router` from context
    contextTypes: {
        router: React.PropTypes.object
    },
    handleSubmit(event) {
        const path = `/repos/${userName}/${repo}`
        this.context.router.push(path)
    },
})

路由的钩子

个路由都有Enter和Leave钩子,用户进入或离开该路由时触发。


<Route path="inbox" component={Inbox}>
<Redirect from="messages/:id" to="/messages/:id" />

上面的代码中,如果用户离开/messages/:id,进入/about时,会依次触发以下的钩子。

/messages/:id的onLeave
/inbox的onLeave
/about的onEnter
下面是一个例子,使用onEnter钩子替代组件。


 replace(`/messages/${params.id}`)
    } 
/>

onEnter钩子还可以用来做认证。

const requireAuth = (nextState, replace) => {
    if (!auth.isAdmin()) {
        // Redirect to Home page if not an Admin
        replace({ pathname: '/' })
    }
}
export const AdminRoutes = () => {
return (
    
)
}

下面是一个高级应用,当用户离开一个路径的时候,跳出一个提示框,要求用户确认是否离开。

const Home = withRouter(
React.createClass({
    componentDidMount() {
    this.props.router.setRouteLeaveHook(
        this.props.route, 
        this.routerWillLeave
    )
    },

    routerWillLeave(nextLocation) {
    // 返回 false 会继续停留当前页面,
    // 否则,返回一个字符串,会显示给用户,让其自己决定
    if (!this.state.isSaved)
        return '确认要离开?';
    },
})
)

上面代码中,setRouteLeaveHook方法为Leave钩子指定routerWillLeave函数。该方法如果返回false,将阻止路由的切换,否则就返回一个字符串,提示用户决定是否要切换。

ES6-React

加载模块

//不需要提前引入任何文件
import React from "react";
import ReactDOM from "react-dom"

创建组件:使用类来创建组件代替React.createClass

import React,{Component} from "react";
class MyComponent extends Component{
    //组件内部代码
}

State/Props/PropTypes:es6允许将props和propTypes当作静态属性在类外初始化

关于 PropTypes:

组件的属性可以接受任意值,字符串、对象、函数等等都可以。有时,我们需要一种机制,验证别人使用组件时,提供的参数是否符合要求。

组件类的PropTypes属性,就是用来验证组件实例的属性是否符合要求,PropTypes 告诉 React,这个 title 属性是必须的,而且它的值必须是字符串。

此外,defaultProps 方法可以用来设置组件属性的默认值。

class MyComponent extends React.Component{}
MyComponent.defaultProps={
    name:"SunnyChuan",
    age:22
};
MyComponent.propTypes={
    name:React.PropTypes.string.isRequired,
    age:React.PropTypes.number.isRequired
};

es7支持直接在类中使用变量表达式,这也是我推荐的写法

class MyComponent extends React.Component{
    static defaultProps={
        name:"SunnyChuan",
        age:22
    }
    static propTypes={
        name:React.PropTypes.string.isRequired,
        age:React.PropTypes.number.isRequired
    }
}

state和前两个不同,它不是静态的

class MyComponent extends React.Component{
    static defaultProps={
        name:"SunnyChuan",
        age:22
    }
    state={
        isMarried:false
    }
    static propTypes={
        name:React.PropTypes.string.isRequired,
        age:React.PropTypes.number.isRequired
    }
}

数据

通过这种user={user}的方式传递数据,如果子组件想要获得name这个数据,就需要通过this.props.user.name的方式获取数据,看下面这种方式

// Parent Component's render method
render: function() {
  const user = {
    name: 'Brad',
    occupation: 'Web Development',
    state: 'Arizona'
  };
  return ();
}

这种方式,子组件想要获得name或全部数据,需要this.props.name,前提是要像这样name={user.name} occupation={user.occupation} state={user.state}哦,也是挺麻烦的,所以使用es6的特性之一,解构赋值方便了许多哦!看下面的代码块:

// Parent Component's render method
render: function() {
  const user = {
    name: 'Brad',
    occupation: 'Web Development',
    state: 'Arizona'
  };
  return ();
}

这样,子组件只需要this.props.name,this.props.occupation,this.props.state方式就能获得传递过来的数据了

函数

React.createClass本身接收的是一个对象,对于对象中的方法,es6允许使用key(){}的形式取代key:function(){}

class MyComponent extends React.Component{
    state={
        count:0
    }
    handleChange(){
        this.setState({count:this.state.count+1});
    }
}

需要注意的是,由于使用class创建组件,react不会再自动帮我们绑定作用域了,我们需要自己手动解决

class MyComponent extends React.Component{
    state={
        count:0
    }
    handleChange(){
        this.setState({count:this.state.count+1});
    }
    render(){
        return (
            

当前计数是:{this.state.count}

) } }

如果你觉得这种每次都需要绑定的方法太麻烦,也可以在构造函数中去绑定

class MyComponent extends React.Component{
    constructor(props){
        super(props);
        this.handleChange=this.handleChange.bind(this);
    }
    state={
        count:0
    }
    handleChange(){
        this.setState({count:this.state.count+1});
    }
    render(){
        return (
            

当前计数是:{this.state.count}

) } }

如果你觉得这种方式也麻烦,可以使用es6的箭头函数(自动绑定作用域),但是前提是你的环境要支持es7,因为箭头函数相当于表达式声明函数的简写,只有es7支持在类中这么使用(类中使用表达式state/props/propTypes也只有es7支持)

class MyComponent extends React.Component{
  state={
    count:0
  }
  handleChange=()=>{
    this.setState({count:this.state.count+1});
  }
  render(){
    return (
      

当前计数是:{this.state.count}

) } }

组件生命周期

所有的组件生命周期都可以当作普通函数使用上述三种方式编写,componentWillMount比较特殊,它还可以在构造函数中编写

class MyComponent extends React.Component{
  componentWillMount(){
    console.log("Hello SunnyChuan");
  }
}
//二者等价
class MyComponent extends React.Component{
  constructor(props){
    console.log("Hello SunnyChuan")
  }
}

扩展操作符

使用react开发最常见的问题就是父组件要传给子组件的属性较多时比较麻烦

class MyComponent extends React.Component{
//假设MyComponent已经有了name和age属性
  render(){
    return (
      
     )
  }
}

使用扩展操作符可以变得很简单

class MyComponent extends React.Component{
//假设MyComponent已经有了name和age属性
  render(){
    return (
      
     )
  }
}

上述方式是将父组件的所有属性都传递下去,如果这其中有些属性我不需要传递呢?也很简单

class MyComponent extends React.Component{
//假设MyComponent有很多属性,而name属性不需要传递给子组件
  var {name,...MyProps}=this.props;  // var {name,...MyProps} 表示提取指定对像的属性
  render(){
    return (
      
     )
  }
}

上述方法最常用的场景就是父组件的class属性需要被单独提取出来作为某个元素的class,而其他属性需要传递给子组件。

模块化开发组件

说了这么多,个人认为es6+react最吸引人的地方就是模块化开发,将每个小(大)组件当作一个模块

//father.js
import React from "react";
import ReactDOM from "react-dom";
import {SonComponent} from "son.js";
class FatherComponent extends React.Component{ 
    //省去中间的业务逻辑
    render(){
        return ();
    }
}
ReactDOM.render(,document.getElementById("ss"));

//son.js
import React from "react";
class SonComponent extends React.Component{
    //省去中间的业务逻辑
    render(){
        return (

"SunnyChuan"

); } } export {SonComponent};

如果你把子组件的导出设置为default export,那么在导入时就不必再加{}
//father.js
import SonComponent from “son.js”;

//son.js
export default class SonComponent extends React.Component{
    //省去中间的业务逻辑
}

花一点时间学习es6(7)+react的开发方式,你会更加喜欢使用react。

什么是CSS Modules?

这到底是什么呢?我们为什么要这么做呢?我们很快就进行介绍。首先,不要忘记HTML和CSS的工作原理。在HTML中一个类添加:

    

An example heading

在CSS中这个class的定义如下:

    .title {
        background-color: red;
    }

只要CSS被添加到HTML文档上,那个

的背景色就是红色。我们不需要人为处理CSS和HTML文件。浏览器本身自己就理解这些文件的格式。
CSS Modules 和上面的方法不一样。我们不写纯HTML,我们需要在一个类似index.js这样的Javascript 文件中取写我们所有的标签。这里有一个例子来说明这是怎么回事(我们之后将会去看更多真实的实例):

    import styles from "./styles.css";

    element.innerHTML = 
        `

An example heading

`;

在我们构建的步骤中,编译器将会搜索我们导入的styles.css文件,然后到我们刚刚写的js文件中,通过styles.title使得.title class可用。我们的构建步骤将会同时处理这些东西成为新的,分离的HTML和CSS文件,并且用一个新的字符串去替换HTML和CSS选择器的class。
通过构建工具生成的HTML也许像下面这样:

    

An example heading

通过构建工具生成的CSS也许像下面这样:

    ._styles__title_309571057{
        background-color: red;
    }

全局作用域

CSS Modules 允许使用:global(.className)的语法,声明一个全局规则。凡是这样声明的class,都不会被编译成哈希字符串。
App.css加入一个全局class。

.title {
  color: red;
}

:global(.title) {
  color: green;
}

App.js使用普通的class的写法,就会引用全局class。

import React from 'react';
import styles from './App.css';

export default () => {
  return (
    

Hello World

); };

运行这个示例。打开 http://localhost:8080,应该会看到h1标题显示为绿色。

Fetch

Fetch API是基于Promise设计,Promise 对象用于表示一个异步操作的最终状态(完成或失败),以及其返回的值。

Promise 创建

下面是创建 promise 的步骤:

var promise = new Promise(function(resolve, reject) {
	// 异步处理
	// 处理结束后、调用resolve 或 reject
});

Promise 构造函数包含一个参数和一个带有 resolve(解析)和 reject(拒绝)两个参数的回调。在回调中执行一些操作(例如异步),如果一切都正常,则调用 resolve,否则调用 reject。
想要某个函数?拥有promise功能,只需让其返回一个promise即可。

function myAsyncFunction(url) {
	return new Promise((resolve, reject) => {
		const xhr = new XMLHttpRequest();
		xhr.open("GET", url);
		xhr.onload = () => resolve(xhr.responseText);
		xhr.onerror = () => reject(xhr.statusText);
		xhr.send();
	});
};

Promise 使用

对于已经实例化过的 promise 对象可以调用 promise.then() 方法,传递 resolve 和 reject 方法作为回调。
promise.then() 是 promise 的方法: promise.then(onFulfilled, onRejected)
promise简化了对error的处理,最为常用可以这样写:promise.then(onFulfilled).catch(onRejected)

Fetch使用说明

fetch(url, options).then(function(response) { 
	// handle HTTP response
}, function(error) {
	 // handle network error
})
说明:
  1. fetch api返回的是一个promise对象
  2. Options:
    method(String): HTTP请求方法,默认为GET
    body(String): HTTP的请求参数
    headers(Object): HTTP的请求头,默认为{}
    credentials(String): 默认为omit,忽略的意思,也就是不带cookie;还有两个参数,same-origin,意思就是同源请求带cookie;include,表示无论跨域还是同源请求都会带cookie
  3. 第一个then函数里面处理的是response的格式
    status(number): HTTP返回的状态码,范围在100-599之间
    statusText(String): 服务器返回的状态文字描述,例如Unauthorized,上图中返回的是Ok
    ok(Boolean): 如果状态码是以2开头的,则为true
    headers: HTTP请求返回头
    body: 返回体,这里有处理返回体的一些方法
    text(): 将返回体处理成字符串类型
    json(): 返回结果和 JSON.parse(responseText)一样
    blob(): 返回一个Blob,Blob对象是一个不可更改的类文件的二进制数据
    arrayBuffer()
    formData()
  4. 如caniuse所示,IE浏览器完全不支持fetch,移动端的很多浏览器也不支持,所以,如果要在这些浏览器上使用Fetch,就必须使用fetch polyfill
  5. cookie传递
    必须在header参数里面加上credentials: ‘include’,才会如xhr一样将当前cookies带到请求中去
  6. fetch和xhr的不同
    fetch虽然底层,但是还是缺少一些常用xhr有的方法,比如能够取消请求(abort)方法
    fetch在服务器返回4xx、5xx时是不会抛出错误的,这里需要手动通过,通过response中的ok字段和status字段来判断

你可能感兴趣的:(Html)