目录
一、HTML 模板
二、ReactDOM.render()
三、JSX 语法
四、组件
五、this.props.children
六、PropTypes
七、获取真实的DOM节点
八、this.state
九、表单
十、组件的生命周期
constructor()
componentWillMount()
render()
componentDidMount()
组件生命周期 - 运行阶段(Updating)
componentWillReceiveProps()
shouldComponentUpdate()
componentWillUpdate()
组件生命周期 - 卸载阶段(Unmounting)
componentWillUnmount()
十一、Ajax
补充:
JSX的注意点
React组件
React创建组件的两种方式
JavaScript函数创建
class创建
给组件传递数据 - 父子组件传递数据
props和state
props
state
评论列表案例
style样式
state和setState
组件绑定事件
React中的事件机制 - 推荐
事件绑定中的this
通过bind绑定
通过箭头函数绑定
受控组件
React 单向数据流
组件通讯
react-router
基本概念说明
使用步骤
注意点
路由参数
路由跳转
fetch
fetch 基本使用
跨域获取数据的三种常用方式
JSONP
代理
CORS - 服务器端配合
redux
核心
本周由于刚入职的公司需要写react,自己也从网上借鉴了一些资料整合到自己的博客中,以便下次学习的时候方便回忆!
使用 React 的网页源码,结构大致如下。
上面代码有两个地方需要注意。首先,最后一个 标签的
type
属性为 text/babel
。这是因为 React 独有的 JSX 语法,跟 JavaScript 不兼容。凡是使用 JSX 的地方,都要加上 type="text/babel"
。
其次,上面代码一共用了三个库: react.js
、react-dom.js
和 Browser.js
,它们必须首先加载。其中,react.js
是 React 的核心库,react-dom.js
是提供与 DOM 相关的功能,Browser.js
的作用是将 JSX 语法转为 JavaScript 语法,这一步很消耗时间,实际上线的时候,应该将它放到服务器完成。
$ babel src --out-dir build
上面命令可以将 src
子目录的 js
文件进行语法转换,转码后的文件全部放在 build
子目录
ReactDOM.render 是 React 的最基本方法,用于将模板转为 HTML 语言,并插入指定的 DOM 节点。
demo1:
上面代码将一个 h1
标题,插入 example
节点。
输出结果如下:
上一节的代码, HTML 语言直接写在 JavaScript 语言之中,不加任何引号,这就是 JSX 的语法,它允许 HTML 与 JavaScript 的混写。
demo2:
上面代码体现了 JSX 的基本语法规则:遇到 HTML 标签(以 <
开头),就用 HTML 规则解析;遇到代码块(以 {
开头),就用 JavaScript 规则解析。上面代码的运行结果如下:
JSX 允许直接在模板插入 JavaScript 变量。如果这个变量是一个数组,则会展开这个数组的所有成员
demo3:
上面代码的arr
变量是一个数组,结果 JSX 会把它的所有成员,添加到模板,运行结果如下
React 允许将代码封装成组件(component),然后像插入普通 HTML 标签一样,在网页中插入这个组件。React.createClass 方法就用于生成一个组件类(查看 demo04)。
上面代码中,变量 HelloMessage
就是一个组件类。模板插入
时,会自动生成 HelloMessage
的一个实例(下文的"组件"都指组件类的实例)。所有组件类都必须有自己的 render
方法,用于输出组件。
注意,组件类的第一个字母必须大写,否则会报错,比如HelloMessage
不能写成helloMessage
。另外,组件类只能包含一个顶层标签,否则也会报错。
var HelloMessage = React.createClass({
render: function() {
return
Hello {this.props.name}
some text
;
}
});
上面代码会报错,因为HelloMessage
组件包含了两个顶层标签:h1
和p
。
组件的用法与原生的 HTML 标签完全一致,可以任意加入属性,比如
,就是 HelloMessage
组件加入一个 name
属性,值为 John
。组件的属性可以在组件类的 this.props
对象上获取,比如 name
属性就可以通过 this.props.name
读取。上面代码的运行结果如下。
添加组件属性,有一个地方需要注意,就是 class
属性需要写成 className
,for
属性需要写成 htmlFor
,这是因为 class
和 for
是 JavaScript 的保留字。
this.props
对象的属性与组件的属性一一对应,但是有一个例外,就是 this.props.children
属性。它表示组件的所有子节点(查看 demo05)
上面代码的 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
。更多的 React.Children
的方法,请参考官方文档。
组件的属性可以接受任意值,字符串、对象、函数等等都可以。有时,我们需要一种机制,验证别人使用组件时,提供的参数是否符合要求。
组件类的PropTypes
属性,就是用来验证组件实例的属性是否符合要求(查看 demo06)
上面的Mytitle
组件有一个title
属性。PropTypes
告诉 React,这个 title
属性是必须的,而且它的值必须是字符串。现在,我们设置 title
属性的值是一个数值。
这样一来,title
属性就通不过验证了。控制台会显示一行错误信息。
Warning: Failed propType: Invalid prop `title` of type `number` supplied to `MyTitle`, expected `string`.
更多的PropTypes
设置,可以查看官方文档。
此外,getDefaultProps
方法可以用来设置组件属性的默认值。
var MyTitle = React.createClass({
getDefaultProps : function () {
return {
title : 'Hello World'
};
},
render: function() {
return {this.props.title}
;
}
});
ReactDOM.render(
,
document.body
);
上面代码会输出"Hello World"。
组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫做虚拟 DOM (virtual DOM)。只有当它插入文档以后,才会变成真实的 DOM 。根据 React 的设计,所有的 DOM 变动,都先在虚拟 DOM 上发生,然后再将实际发生变动的部分,反映在真实 DOM上,这种算法叫做 DOM diff ,它可以极大提高网页的性能表现。
但是,有时需要从组件获取真实 DOM 的节点,这时就要用到 ref
属性(查看 demo07)
上面代码中,组件 MyComponent
的子节点有一个文本输入框,用于获取用户的输入。这时就必须获取真实的 DOM 节点,虚拟 DOM 是拿不到用户输入的。为了做到这一点,文本输入框必须有一个 ref
属性,然后 this.refs.[refName]
就会返回这个真实的 DOM 节点。
需要注意的是,由于 this.refs.[refName]
属性获取的是真实 DOM ,所以必须等到虚拟 DOM 插入文档以后,才能使用这个属性,否则会报错。上面代码中,通过为组件指定 Click
事件的回调函数,确保了只有等到真实 DOM 发生 Click
事件之后,才会读取 this.refs.[refName]
属性。
React 组件支持很多事件,除了 Click
事件以外,还有 KeyDown
、Copy
、Scroll
等,完整的事件清单请查看官方文档。
组件免不了要与用户互动,React 的一大创新,就是将组件看成是一个状态机,一开始有一个初始状态,然后用户互动,导致状态变化,从而触发重新渲染 UI (查看 demo08 )。
上面代码是一个 LikeButton
组件,它的 getInitialState
方法用于定义初始状态,也就是一个对象,这个对象可以通过 this.state
属性读取。当用户点击组件,导致状态变化,this.setState
方法就修改状态值,每次修改以后,自动调用 this.render
方法,再次渲染组件。
由于 this.props
和 this.state
都用于描述组件的特性,可能会产生混淆。一个简单的区分方法是,this.props
表示那些一旦定义,就不再改变的特性,而 this.state
是会随着用户互动而产生变化的特性。
用户在表单填入的内容,属于用户跟组件的互动,所以不能用 this.props
读取(查看 demo9 )
上面代码中,文本输入框的值,不能用 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()
constructor()
的参数props
获取
class Greeting extends React.Component {
constructor(props) {
// 获取 props
super(props)
// 初始化 state
this.state = {
count: props.initCount
}
}
}
// 初始化 props
// 语法:通过静态属性 defaultProps 来初始化props
Greeting.defaultProps = {
initCount: 0
};
同步
地设置状态将不会触发重渲染setState()
方法来改变状态值componentWillMount() {
console.warn(document.getElementById('btn')) // null
this.setState({
count: this.state.count + 1
})
}
注意:不要在render方法中调用 setState()
方法,否则会递归渲染
render()
,render()
又重新改变状态render() {
console.warn(document.getElementById('btn')) // null
return (
{
this.state.count === 4
? null
:
}
)
}
setState()
修改状态的值componentDidMount() {
// 此时,就可以获取到组件内部的DOM对象
console.warn('componentDidMount', document.getElementById('btn'))
}
props
或者state
改变的时候,都会触发运行阶段的函数props
前触发这个方法props
值this.props
获取到上一次的值this.props
和nextProps
并在该方法中使用this.setState()
处理状态改变state
不会触发该方法componentWillReceiveProps(nextProps) {
console.warn('componentWillReceiveProps', nextProps)
}
true
重新渲染,否则不渲染false
,那么,后续render()
方法不会被调用// - 参数:
// - 第一个参数:最新属性对象
// - 第二个参数:最新状态对象
shouldComponentUpdate(nextProps, nextState) {
console.warn('shouldComponentUpdate', nextProps, nextState)
return nextState.count % 2 === 0
}
componentWillUpdate(nextProps, nextState) {
console.warn('componentWillUpdate', nextProps, nextState)
}
作用:在卸载组件的时候,执行清理工作,比如
componentDidMount
创建的DOM对象此外,React 还提供两种特殊状态的处理函数。
- componentWillReceiveProps(object nextProps):已加载组件收到新的参数时调用
- shouldComponentUpdate(object nextProps, object nextState):组件判断是否重新渲染时调用
这些方法的详细说明,可以参考官方文档。下面是一个例子(查看 demo10 )。
上面代码在hello
组件加载以后,通过 componentDidMount
方法设置一个定时器,每隔100毫秒,就重新设置组件的透明度,从而引发重新渲染。
另外,组件的style
属性的设置方式也值得注意,不能写成
style="opacity:{this.state.opacity};"
而要写成
style={{opacity: this.state.opacity}}
这是因为 React 组件样式是一个对象,所以第一重大括号表示这是 JavaScript 语法,第二重大括号表示样式对象。
组件的数据来源,通常是通过 Ajax 请求从服务器获取,可以使用 componentDidMount
方法设置 Ajax 请求,等到请求成功,再用 this.setState
方法重新渲染 UI (查看 demo11 )。
上面代码使用 jQuery 完成 Ajax 请求,这是为了便于说明。React 本身没有任何依赖,完全可以不用jQuery,而使用其他库。
我们甚至可以把一个Promise对象传入组件,请看Demo12。
注意 1: 如果在 JSX 中给元素添加类, 需要使用 className
代替 class
htmlFor
代替{/* 中间是注释的内容 */}
React 组件可以让你把UI分割为独立、可复用的片段,并将每一片段视为相互独立的部分。
props
),并且返回一个React对象,用来描述展示在页面中的内容函数式组件 和 class 组件的使用场景说明: 1 如果一个组件仅仅是为了展示数据,那么此时就可以使用 函数组件 2 如果一个组件中有一定业务逻辑,需要操作数据,那么就需要使用 class 创建组件,因为,此时需要使用 state
null
()
包裹,避免换行问题function Welcome(props) {
return (
// 此处注释的写法
{/* 此处 注释的写法 必须要{}包裹 */}
Shopping List for {props.name}
- Instagram
- WhatsApp
)
}
ReactDOM.render(
,
document.getElementById('app')
)
在es6中class仅仅是一个语法糖,不是真正的类,本质上还是构造函数+原型 实现继承
// ES6中class关键字的简单使用
// - **ES6中的所有的代码都是运行在严格模式中的**
// - 1 它是用来定义类的,是ES6中实现面向对象编程的新方式
// - 2 使用`static`关键字定义静态属性
// - 3 使用`constructor`构造函数,创建实例属性
// - [参考](http://es6.ruanyifeng.com/#docs/class)
// 语法:
class Person {
// 实例的构造函数 constructor
constructor(age){
// 实例属性
this.age = age
}
// 在class中定义方法 此处为实例方法 通过实例打点调用
sayHello () {
console.log('大家好,我今年' + this.age + '了');
}
// 静态方法 通过构造函数打点调用 Person.doudou()
static doudou () {
console.log('我是小明,我新get了一个技能,会暖床');
}
}
// 添加静态属性
Person.staticName = '静态属性'
// 实例化对象
const p = new Person(19)
// 实现继承的方式
class American extends Person {
constructor() {
// 必须调用super(), super表示父类的构造函数
super()
this.skin = 'white'
this.eyeColor = 'white'
}
}
// 创建react对象
// 注意:基于 `ES6` 中的class,需要配合 `babel` 将代码转化为浏览器识别的ES5语法
// 安装:`npm i -D babel-preset-env`
// react对象继承字React.Component
class ShoppingList extends React.Component {
constructor(props) {
super(props)
}
// class创建的组件中 必须有rander方法 且显示return一个react对象或者null
render() {
return (
Shopping List for {this.props.name}
- Instagram
- WhatsApp
)
}
}
只读的对象
叫做 props
,无法给props添加属性props
props
对象中的属性function Welcome(props){
// props ---> { username: 'zs', age: 20 }
return (
Welcome React
姓名:{props.username}----年龄是:{props.age}
)
}
// 给 Hello组件 传递 props:username 和 age(如果你想要传递numb类型是数据 就需要向下面这样)
ReactDOM.reander( , ......)
封装组件到独立的文件中
// 创建Hello2.js组件文件
// 1. 引入React模块
// 由于 JSX 编译后会调用 React.createElement 方法,所以在你的 JSX 代码中必须首先拿到React。
import React from 'react'
// 2. 使用function构造函数创建组件
function Hello2(props){
return (
这是Hello2组件
这是大大的H1标签,我大,我骄傲!!!
这是小小的h6标签,我小,我傲娇!!!
)
}
// 3. 导出组件
export default Hello2
// app.js中 使用组件:
import Hello2 from './components/Hello2'
props
props
是只读的,无法给props
添加或修改属性props.children
:获取组件的内容,比如:
组件内容
中的 组件内容
// props 是一个包含数据的对象参数,不要试图修改 props 参数
// 返回值:react元素
function Welcome(props) {
// 返回的 react元素中必须只有一个根元素
return hello, {props.name}
}
class Welcome extends React.Component {
constructor(props) {
super(props)
}
render() {
return Hello, {this.props.name}
}
}
状态即数据
组件内部
使用的数据class
创建的组件才具有状态注意:不要在 state
中添加 render()
方法中不需要的数据,会影响渲染性能!
注意:不要在 render()
方法中调用 setState() 方法来修改state
的值
this.state.name = 'rose'
方式设置state(不推荐!!!!)// 例:
class Hello extends React.Component {
constructor() {
// es6继承必须用super调用父类的constructor
super()
this.state = {
gender: 'male'
}
}
render() {
return (
性别:{ this.state.gender }
)
}
}
和
[
{ user: '张三', content: '哈哈,沙发' },
{ user: '张三2', content: '哈哈,板凳' },
{ user: '张三3', content: '哈哈,凉席' },
{ user: '张三4', content: '哈哈,砖头' },
{ user: '张三5', content: '哈哈,楼下山炮' }
]
// 属性扩展
// 1. 直接写行内样式:
// 2. 抽离为对象形式
var styleH3 = {color:'blue'}
var styleObj = {
liStyle:{border:'1px solid red', fontSize:'12px'},
h3Style:{color:'green'}
}
评论内容:{props.content}
// 3. 使用样式表定义样式:
import '../css/comment.css'
评论人:{props.user}
setState()
方法修改状态,状态改变后,React会重新渲染组件// 修改state(不推荐使用)
// https://facebook.github.io/react/docs/state-and-lifecycle.html#do-not-modify-state-directly
this.state.test = '这样方式,不会重新渲染组件';
constructor(props) {
super(props)
// 正确姿势!!!
// -------------- 初始化 state --------------
this.state = {
count: props.initCount
}
}
componentWillMount() {
// -------------- 修改 state 的值 --------------
// 方式一:
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
}, function(){
// 由于 setState() 是异步操作,所以,如果想立即获取修改后的state
// 需要在回调函数中获取
// https://doc.react-china.org/docs/react-component.html#setstate
});
// 方式二:
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
}
})
// 或者 - 注意: => 后面需要带有小括号,因为返回的是一个对象
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}))
}
onClick
绑定2 JS原生方式绑定(通过 ref
获取元素)
ref
是React提供的一个特殊属性ref
的使用说明:react refonClick
用来绑定单击事件
bind
绑定箭头函数
绑定bind
能够调用函数,改变函数内部this的指向,并返回一个新函数bind
第一个参数为返回函数中this的指向,后面的参数为传给返回函数的参数// 自定义方法:
handleBtnClick(arg1, arg2) {
this.setState({
msg: '点击事件修改state的值' + arg1 + arg2
})
}
render() {
return (
{this.state.msg}
)
}
在构造函数中使用bind:
constructor() {
super()
this.handleBtnClick = this.handleBtnClick.bind(this)
}
// render() 方法中:
箭头函数
中的this由所处的环境决定,自身不绑定this { this.handleBtnClick('参数1', '参数2') }
} />
handleBtnClick(arg1, arg2) {
this.setState({
msg: '在构造函数中绑定this并传参' + arg1 + arg2
});
}
在HTML当中,像input
,textarea
和select
这类表单元素会维持自身状态,并根据用户输入进行更新。
在React中,可变的状态通常保存在组件的state
中,并且只能用setState()
方法进行更新.
React根据初始状态渲染表单组件,接受用户后续输入,改变表单组件内部的状态。
因此,将那些值由React控制的表单元素称为:受控组件。
受控组件的特点:
// 模拟实现文本框数据的双向绑定
// 当文本框内容改变的时候,触发这个事件,重新给state赋值
handleTextChange = event => {
console.log(event.target.value)
this.setState({
msg: event.target.value
})
}
react中的单向数据流动:
1 数据应该是从上往下流动的,也就是由父组件将数据传递给子组件
2 数据应该是由父组件提供,子组件要使用数据的时候,直接从子组件中获取在我们的评论列表案例中:数据是由CommentList组件(父组件)提供的
子组件 CommentItem 负责渲染评论列表,数据是由 父组件提供的
子组件 CommentForm 负责获取用户输入的评论内容,最终也是把用户名和评论内容传递给了父组件,由父组件负责处理这些数据( 把数据交给 CommentItem 由这个组件负责渲染 )
props
npm i -S react-router-dom
Router
组件本身只是一个容器,真正的路由要通过Route组件
定义2 使用
作为根容器,包裹整个应用(JSX)
作为链接地址,并指定to
属性
展示路由内容// 1 导入组件
import {
HashRouter as Router,
Link, Route
} from 'react-router-dom'
// 2 使用
// 3 设置 Link
首页
电影
关于
// 4 设置 Route
// exact 表示:绝对匹配(完全匹配,只匹配:/)
:作为整个组件的根元素,是路由容器,只能有一个唯一的子元素
:类似于vue中的
标签,to
属性指定路由地址
:类似于vue中的
,指定路由内容(组件)展示位置Route
中的path属性来配置路由参数this.props.match.params
获取// 配置路由参数
// 获取路由参数
const type = this.props.match.params.movieType
history.push()
方法用于在JS中实现页面跳转history.go(-1)
用来实现页面的前进(1)和后退(-1)this.props.history.push('/movie/movieDetail/' + movieId)
fetch()
方法返回一个Promise
对象/*
通过fetch请求回来的数据,是一个Promise对象.
调用then()方法,通过参数response,获取到响应对象
调用 response.json() 方法,解析服务器响应数据
再次调用then()方法,通过参数data,就获取到数据了
*/
fetch('/api/movie/' + this.state.movieType)
// response.json() 读取response对象,并返回一个被解析为JSON格式的promise对象
.then((response) => response.json())
// 通过 data 获取到数据
.then((data) => {
console.log(data);
this.setState({
movieList: data.subjects,
loaing: false
})
})
npm i -S fetch-jsonp
JSONP
实现跨域获取数据,只能获取GET请求fetch-jsonp
/* movielist.js */
fetchJsonp('https://api.douban.com/v2/movie/in_theaters')
.then(rep => rep.json())
.then(data => { console.log(data) })
webpack-dev-server
代理配置如下:// webpack-dev-server的配置
devServer: {
// https://webpack.js.org/configuration/dev-server/#devserver-proxy
// https://github.com/chimurai/http-proxy-middleware#http-proxy-options
// http://www.jianshu.com/p/3bdff821f859
proxy: {
// 使用:/api/movie/in_theaters
// 访问 ‘/api/movie/in_theaters’ ==> 'https://api.douban.com/v2/movie/in_theaters'
'/api': {
// 代理的目标服务器地址
target: 'https://api.douban.com/v2',
// https请求需要该设置
secure: false,
// 必须设置该项
changeOrigin: true,
// '/api/movie/in_theaters' 路径重写为:'/movie/in_theaters'
pathRewrite: {"^/api" : ""}
}
}
}
/* movielist.js */
fetch('/api/movie/in_theaters')
.then(function(data) {
// 将服务器返回的数据转化为 json 格式
return data.json()
})
.then(function(rep) {
// 获取上面格式化后的数据
console.log(rep);
})
// 通过Express的中间件来处理所有请求
app.use('*', function (req, res, next) {
// 设置请求头为允许跨域
res.header('Access-Control-Allow-Origin', '*');
// 设置服务器支持的所有头信息字段
res.header('Access-Control-Allow-Headers', 'Content-Type,Content-Length, Authorization,Accept,X-Requested-With');
// 设置服务器支持的所有跨域请求的方法
res.header('Access-Control-Allow-Methods', 'POST,GET');
// next()方法表示进入下一个路由
next();
});
Action:行为的抽象,视图中的每个用户交互都是一个action
Reducer:行为响应的抽象,也就是:根据action行为,执行相应的逻辑操作,更新state
Store:
getState()
:获取statedispatch(action)
:更新state/* action */
// 在 redux 中,action 就是一个对象
// action 必须提供一个:type属性,表示当前动作的标识
// 其他的参数:表示这个动作需要用到的一些数据
{ type: 'ADD_TODO', name: '要添加的任务名称' }
// 这个动作表示要切换任务状态
{ type: 'TOGGLE_TODO', id: 1 }
/* reducer */
// 第一个参数:表示状态(数据),我们需要给初始状态设置默认值
// 第二个参数:表示 action 行为
function todo(state = [], action) {
switch(action.type) {
case 'ADD_TODO':
state.push({ id: Math.random(), name: action.name, completed: false })
return state
case 'TOGGLE_TODO':
for(var i = 0; i < state.length; i++) {
if (state[i].id === action.id) {
state[i].completed = !state[i].completed
break
}
}
return state
default:
return state
}
}
// 要执行 ADD_TODO 这个动作:
dispatch( { type: 'ADD_TODO', name: '要添加的任务名称' } )
// 内部会调用 reducer
todo(undefined, { type: 'ADD_TODO', name: '要添加的任务名称' })