React 入门与实战
react组件
虚拟DOM的概念 这是React性能高效的核心算法
React
为此引入了虚拟DOM
(Virtual 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
一个组件应该具有如下特征:
- 可组合(
Composeable
):一个组件易于和其它组件一起使用,或者嵌套在另一个组件内部。如果一个组件内部创建了另一个组件,那么说父组件拥有它创建的子组件,通过这个特性,一个复杂的UI
可以拆分成多个简单的UI
组件. - 可重用(
Reusable
):每个组件都是具有独立功能的,它可以被使用在多个UI
场景 - 可维护(
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
的语法,它允许HTML
与JavaScript
的混写。 -
JSX
的基本语法规则:遇到HTML
标签(以<
开头),就用HTML
规则解析;遇到代码块(以{
开头),就用JavaScript
规则解析 - JSX 中的注释
{/*注释*/}
export default class BodyIndex extends React.Component{
render(){
var userName = 'xiaoxiong';
var boolInput;
var html1 = 'Mooc Lesson';
//对 进行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 Lesson' 不会被解析为空格
<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
会触发一次render
,React
可能会合并操作,再一次性进行render
)
shouldComponentUpdate --> componentWillUpdate --> render --> componentDidUpdate
- 父组件发生更新(一般就是
props
发生改变,但是就算props
没有改变或者父子组件之间没有数据交换也会触发render
)
componentWillReceiveProps --> shouldComponentUpdate --> componentWillUpdate --> render --> componentDidUpdate
- 调用
this.forceUpdate
componentWillUpdate --> render --> componentDidUpdate
总结
-
constructor
、componentWillMount
、componentDidMount
只有第一次渲染时候会被调用 -
componentWillUpdate
、componentDidUpdate
、shouldComponentUpdate
在以后的每次更新渲染之后都会被调用
考虑到性能的问题,如果有些属性的变化,不需要重新刷新页面,我们是使用 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
属性需要写成 className
,for
属性需要写成 htmlFor
,这是因为 class
和 for
是 JavaScript
的保留字。
事件和数据的双向绑定,包含了父子页面之间的参数互传
- 父组件给子组件传递参数,使用
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>
)
}
}
组件的RefsReact
中多数情况下,是通过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
会自动销毁对子组件的引用 - 不要在
Render
或Render
之前对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
文件
npm
的start
是一个特殊的脚本名称,它的特殊性表现在,在命令行中使用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"
}
}
安装依赖
babelifyBabel
其实是一个编译JavaScript
的平台,它的强大之处表现在可以通过编译帮你达到以下目的:
- 下一代的
JavaScript
标准(ES6
,ES7
),这些标准目前并未被当前的浏览器完全的支持 - 使用基于
JavaScript
进行了拓展的语言,比如React
的JSX
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
文件使浏览器可以识别. -
Scss
,less
等CSS
预处理器 - ...
这些改进确实大大的提高了我们的开发效率,但是利用它们开发的文件往往需要进行额外的处理才能让浏览器识别,而手动处理又是非常繁琐的,这就为WebPack
类的工具的出现提供了需求。
webpack
并不强制你使用某种模块化方案,而是通过兼容所有模块化方案让你无痛接入项目,当然这也是webpack
牛逼的地方
webpack
和gulp
的区别
gulp
是工具链、构建工具,可以配合各种插件做js
压缩,css
压缩,less
编译 替代手工实现自动化工作
1.构建工具
2.自动化
3.提高效率用
webpack
是文件打包工具,可以把项目的各种js
文、css
文件等打包合并成一个或多个文件,主要用于模块化方案,预编译模块的方案
1.打包工具
2.模块化识别
3.编译模块代码方案
- 虽然都是前端自动化构建工具,但看他们的定位就知道不是对等的。
-
gulp
严格上讲,模块化不是他强调的东西,他旨在规范前端开发流程。 -
webpack
更是明显强调模块化开发,而那些文件压缩合并、预处理等功能,不过是他附带的功能。
webpack工作方式
Webpack
的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js
),Webpack
将从这个文件开始找到你的项目的所有依赖文件,使用loaders
处理它们,最后打包为一个浏览器可识别的JavaScript
文件。
Loaders
通过使用不同的
loader
,webpack
通过调用外部的脚本或工具可以对各种各样的格式的文件进行处理,比如说分析JSON
文件并把它转换为JavaScript
文件,或者说把下一代的JS
文件(ES6
,ES7
)转换为现代浏览器可以识别的JS
文件。或者说对React
的开发而言,合适的Loaders
可以把React
的JSX
文件转换为JS
文件。
Loaders
需要单独安装并且需要在webpack.config.js
下的modules
关键字下进行配置,Loaders
的配置选项包括以下几方面:
test
:一个匹配loaders
所处理的文件的拓展名的正则表达式(必须)loader
:loader
的名称(必须)include/exclude
:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选);query
:为loaders
提供额外的设置选项(可选)
依赖模块
// MediaQuery 进行移动端适配
var MediaQuery = require('react-responsive');
<MediaQuery query='(min-device-width:1224px)'>
fetch 向后台请求数据
响应式 以1224px
为分界
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。