起初facebook在建设instagram(图片分享)的时候嘞,由于牵扯到一个东东叫数据流,那为了处理数据流而且还要考虑好性能方面的问题嘞,Facebook开始对市场上的各类前端MVC框架去进行一个研究,然而并无看上眼的,因而Facebook以为,仍是本身开发一个才是最棒的,那么他们决定抛开不少所谓的“最佳实践”,从新思考前端界面的构建方式,他们就本身开发了一套,果真大牛创造力仍是很强大的。
复制代码
基于HTML的前端界面开发正变得愈来愈复杂,其本质问题基本均可以归结于如何未来自于服务器端或者用户输入的动态数据高效的反映到复杂的用户界面上。而来自Facebook的React框架正是彻底面向此问题的一个解决方案,按官网描述,其出发点为:用于开发数据不断变化的大型应用程序(Building large applications with data that changes over time)。相比传统型的前端开发,React开辟了一个至关另类的途径,实现了前端界面的高性能高效率开发。
复制代码
React不是一个完整的MVC框架,最多能够认为是MVC中的V(View),甚至React并不很是承认MVC开发模式;
复制代码
React高性能的原理:javascript
在Web开发中咱们总须要将变化的数据实时反应到UI上,这时就须要对DOM进行操做。而复杂或频繁的DOM操做一般是性能瓶颈产生的缘由(如何进行高性能的复杂DOM操做一般是衡量一个前端开发人员技能的重要指标)。css
React为此引入了虚拟DOM(Virtual DOM)的机制:在浏览器端用Javascript实现了一套DOM API。基于React进行开发时全部的DOM构造都是经过虚拟DOM进行,每当数据变化时,React都会从新构建整个DOM树,而后React将当前整个DOM树和上一次的DOM树进行对比,获得DOM结构的区别,而后仅仅将须要变化的部分进行实际的浏览器DOM更新。并且React可以批处理虚拟DOM的刷新,在一个事件循环(Event Loop)内的两次数据变化会被合并,例如你连续的先将节点内容从A-B,B-A,React会认为A变成B,而后又从B变成A UI不发生任何变化,而若是经过手动控制,这种逻辑一般是极其复杂的。html
尽管每一次都须要构造完整的虚拟DOM树,可是由于虚拟DOM是内存数据,性能是极高的,部而对实际DOM进行操做的仅仅是Diff分,于是能达到提升性能的目的。这样,在保证性能的同时,开发者将再也不须要关注某个数据的变化如何更新到一个或多个具体的DOM元素,而只须要关心在任意一个数据状态下,整个界面是如何Render的。数据驱动,声明式前端
咱们之前操做dom的方式是经过document.getElementById()的方式,这样的过程其实是先去读取html的dom结构,将结构转换成变量,再进行操做vue
而reactjs定义了一套变量形式的dom模型,一切操做和换算直接在变量中,这样减小了操做真实dom,性能真实至关的高,和主流MVC框架有本质的区别,并不和dom打交道java
react最核心的思想是将页面中任何一个区域或者元素均可以看作一个组件 componentnode
那么什么是组件呢?react
组件指的就是同时包含了html、css、js、image元素的聚合体webpack
使用react开发的核心就是将页面拆分红若干个组件,而且react一个组件中同时耦合了css、js、image,这种模式整个颠覆了过去的传统的方式es6
其实reactjs的核心内容就是数据绑定,所谓数据绑定指的是只要将一些服务端的数据和前端页面绑定好,开发者只关注实现业务就好了
在vue中,咱们使用render函数来构建组件的dom结构性能较高,由于省去了查找和编译模板的过程,可是在render中利用createElement建立结构的时候代码可读性较低,较为复杂,此时能够利用jsx语法来在render中建立dom,解决这个问题,可是前提是须要使用工具来编译jsx
react开发须要引入多个依赖文件:react.js、react-dom.js,分别又有开发版本和生成版本
react.js中有React对象,帮助咱们建立组件等功能
react-dom.js中有ReactDOM对象,渲染组件的虚拟dom为真实dom的爆发功能
在编写react代码的时候会大量的使用到jsx代码,可是须要编译:
//建立组件
var Hello = React.createClass({
render:function () {
//render函数和Vue组件里的render彻底同样,在vue组件中能够不用编写render函数,这个时候可使用template模板来编写组件的虚拟dom结构,而后vue组件会自动讲模板compile成虚拟dom结构放入到render中执行,可是react须要编写render函数
return (
//jsx语法
<div>asdasd</div>
)
}
})
//利用ReactDOM对象的render方法将组件渲染到某个节点里
ReactDOM.render(<Hello/>,document.getElementById("app"))
复制代码
组件是经过React.createClass建立的(ES5),在es6中直接经过class关键字来建立
组件其实就是一个构造器,每次使用组件都至关于在实例化组件
react的组件必须使用render函数来建立组件的虚拟dom结构
组件须要使用ReactDOM.render方法将其挂载在某一个节点上
组件的首字母必须大写
JSX是一种语法,全称:javascript xml
JSX语法不是必须使用的,可是由于使用了JSX语法以后会下降咱们的开发难度,故而这样的语法又被成为语法糖
在不使用JSX的时候,须要使用React.createElement来建立组件的dom结构,可是这样的写法虽然不须要编译,可是维护和开发的难度很高,且可读性不好
var world = React.createElement('h1',{className:'abc',id:'haha'},[
React.createElement('span',null,'Hello'),
React.createElement('mark',null,'React')
])
//利用ReactDOM对象的render方法将组件渲染到某个节点里
ReactDOM.render(world,document.getElementById("app1"))
复制代码
及时使用了JSX语法了以后,也是须要将其编译成原生的createElement的
JSX就是在js中使用的xml,可是,这里的xml不是真正的xml,只能借鉴了一些xml的语法,例如:
最外层必须有根节点、标签必须闭合
在react里表达式的符号是 "{ }",做用和vue的表达式做用是同样的
想给虚拟dom添加行内样式,须要使用表达式传入样式对象的方式来实现:
<p style = { {color:'red',fontSize:2+'em'} }>Hello world</p>
复制代码
行内样式须要写入一个样式对象,而这个样式对象的位置能够放在不少地方,例如React.createClass的配置项中、render函数里、组件原型上、外链js文件中
React推荐咱们使用行内样式,由于react以为每个组件都是一个独立的总体
其实咱们大多数状况下仍是大量的在为元素添加类名、id以使用某些样式,可是须要注意的是,class须要写成className(由于毕竟是在写类js代码,会收到js规则的如今,而class是关键字)
<p className="bg-p" id="myp" style = { this.style }>Hello world</p>
复制代码
在react中,咱们想要给组件的dom添加事件的话,也是 须要在行内添加的方式,事件名字须要写成小驼峰的方式,值利用表达式传入一个函数便可
注意,在没有渲染的时候,页面中没有真实dom,因此是获取不到dom的
给虚拟dom结构中的节点添加样式。在行内添加,写成驼峰形式,值是一个函数名,须要用{}包裹
handleClick:function () {
alert(1)
},
render:function () {
return (
<div>
<button onClick = {this.handleClick} className="click-btn">click</button>
<button onDoubleClick = {this.handleClick} className="click-btn">click</button>
</div>
)
}
复制代码
将一个组件渲染到某一个节点里的时候,会将这个节点里原有内容覆盖
组件嵌套的方式就是将子组件写入到父组件的模板中去,且react没有Vue中的内容分发机制(slot),因此咱们在一个组件的模板中只能看到父子关系
var Hello = React.createClass({
render(){
return (
<h1>
Hello
<World></World>
</h1>
)
}
})
var World = React.createClass({
render(){
return (
<mark>
World-<Person/>
</mark>
)
}
})
//无状态组件
var Person =function(){
return (<mark>lilei</mark>)
}
ReactDOM.render(<Hello/>,app)
复制代码
注意,react中jsx里的注释要写成{/* */}的方式
React也是基于数据驱动的框架,组件中必然须要承载一些数据,在react中起到这个做用的是属性和状态(props & state)
属性(props) 在组件外部传入,或者内部设置,组件内部经过this.props得到
状态(state) 在组件内部设置或者更改,组件内部经过this.state得到
属性通常是外部传入的,组件内部也能够经过一些方式来初始化的设置,属性不能被组件本身更改
属性是描述性质、特色的,组件本身不能随意更改
使组件拥有属性的方式:
传入数据的时候,除了字符串类型,其余的都应该包上表达式,可是为了规整,全部的数据传递,最好都包上{}
var Gouzi = React.createClass({
render(){
console.log(this)
return (
<div>
<p>个人名字:{this.props.name}</p>
<p>个人性别:{this.props.sex}</p>
<p>个人年龄:{this.props.age}</p>
<p>个人父亲是:{this.props.father}</p>
</div>
)
}
})
let info = {
sex:'male',
father:'狗爸'
}
ReactDOM.render(<Gouzi {...info} name={"大狗子"} age={26}/>,app)
复制代码
父组件在嵌套子组件的时候为子组件传入,传入的方式和上面的方式同样
//父组件的render函数
render(){
return (
<div>
<p>父组件:</p>
<hr/>
<Son name={'大狗子'}/>
<Son name={'二狗子'}/>
</div>
)
}
复制代码
子组件能够经过getDefaultProps来设置默认的属性
getDefaultProps的值是函数,这个函数会返回一个对象,咱们在这里对象里为组件设置默认属性
这种方式设置的属性优先级低,会被外部传入的属性值所覆盖
getDefaultProps:function () {
console.log('getDefaultProps')
return {
name:'狗爸',
sonname:'二狗子'
}
},
//render
<p>我是{this.props.sonname}的父亲-{this.props.name}</p>
复制代码
根据属性或状态,咱们能够在render中的表达式里作一些逻辑判断,可使用||、三元表达式、子执行函数等等
getName(){
return this.props.name || '野狗子'
},
render:function () {
let {name} = this.props
return (
<div>
<p>我是子组件-{this.props.name || '野狗子'}</p>
<p>我是子组件-{this.props.name?this.props.name:'野狗子'}</p>
<p>我是子组件-{this.getName()}</p>
<p>我是子组件-{(function (obj) {
return obj.props.name || '野狗子'
})(this)}</p>
</div>
)
}
复制代码
状态就是组件描述某种显示状况的数据,由组件本身设置和更改,也就是说由组件本身维护,使用状态的目的就是为了在不一样的状态下使组件的显示不一样(本身管理)
在组件中只能经过getInitialState的钩子函数来给组件挂载初始状态,在组件内部经过this.state获取
this.props和this.state是纯js对象,在vue中,data的数据的时候会触发数据的getter和setter,可是react中没有作这样的处理,若是直接更改的话,react是没法得知的,因此,须要使用特殊的更改状态的方法:
setState(params)
在setState中传入一个对象,就会将组件的状态中键值对的部分更改,还能够传入一个函数,这个回调函数必须返回像上面方式同样的一个对象,函数能够接收prevState和props
//1.
let doing = this.state.doing=='学习'+props.knowledge?'玩游戏':'学习'+props.knowledge
this.setState({doing})
//2.
this.setState((prevState,props)=>{
return {
doing:prevState.doing=='学习'+props.knowledge?'玩游戏':'学习'+props.knowledge
}
})
复制代码
<ul style={{display:isMenuShow?'block':'none'}}><li>国内新闻</li></ul>
...
<ul className={isMenuShow?'show':'hide'}><li>国内新闻</li></ul>
复制代码
{
isMenuShow?<ul><li>国内新闻</li></ul>:''
}
复制代码
<ul ref='content'><li>国内新闻</li></ul>
...
this.refs.content.style.display = this.state.isMenuShow?'block':'none'
复制代码
类似点:都是纯js对象,都会触发render更新,都具备肯定性(状态/属性相同,结果相同)
不一样点:
状态只和本身相关,由本身维护
属性不要本身修改,能够从父组件获取,也能够给子组件设置
组件在运行时本身须要修改的数据其实就是状态而已
react中组件也有生命周期,也就是说也有不少钩子函数供咱们使用,下面是生命周期的图示:
[生命周期]
组件是一个构造器,每一次使用组件都至关于在实例化组件,在这个时候,组件就会经历一次生命周期,从实例化实例开始到这个实例销毁的时候,都是一次完整的生命周期
组件的生命周期,咱们会分为三个阶段,初始化、运行中、销毁
实例化组件以后,组件的getDefaultProps钩子函数会执行
这个钩子函数的目的是为组件的实例挂载默认的属性
这个钩子函数只会执行一次,也就是说,只在第一次实例化的时候执行,建立出全部实例共享的默认属性,后面再实例化的时候,不会执行getDefaultProps,直接使用已有的共享的默认属性
理论上来讲,写成函数返回对象的方式,是为了防止实例共享,可是react专门为了让实例共享,只能让这个函数只执行一次
组件间共享默认属性会减小内存空间的浪费,并且也不须要担忧某一个实例更改属性后其余的实例也会更改的问题,由于组件不能本身更改属性,并且默认属性的优先级低。
执行getInitialState为实例挂载初始状态,且每次实例化都会执行,也就是说,每个组件实例都拥有本身独立的状态呢
执行componentWillMount,至关于Vue里的created+beforeMount,这里是在渲染以前最后一次更改数据的机会,在这里更改的话是不会触发render的从新执行
多作一些初始数据的获取
执行render,渲染dom
执行componentDidMount ,至关于Vue里的mounted,多用于操做真实dom
当组件mount到页面中以后,就进入了运行中阶段,在这里有5个钩子函数,可是这5个函数只有在数据(属性、状态)发送改变的时候才会执行
当父组件给子组件传入的属性改变的时候,子组件的这个函数才会执行
当执行的时候,函数接收的参数是子组件接收到的新参数,这个时候,新参数尚未同步到this.props上,多用于判断新属性和原有属性的变化后更改组件的状态
接下来就会执行shouldComponentUpdate,这个函数的做用:
当属性或状态发送改变后控制组件是否要更新,提升性能,返回true就更新,不然不更新,默认返回true
接收nextProp、nextState,根据根据新属性状态和原属性状态做出对比、判断后控制是否更新
componentWillUpdate,在这里,组件立刻就要从新render了,多作一些准备工做,千万千万,不要在这里修改状态,不然会死循环 至关于Vue中的beforeUpdate
render,从新渲染dom
componentDidUpdate,在这里,新的dom结构已经诞生了,至关于Vue里的updated
当组件被销毁以前的一刹那,会触发componentWillUnmount,临死前的挣扎
至关于Vue里的beforeDestroy,因此说通常会作一些擦屁股的事情
为何Vue中有destroyed,而react却没有componentDidUnmount
Vue在调用$destroy方法的时候就会执行beforeDestroy,而后组件被销毁,这个时候组件的dom结构还存在于页面结构中,也就说若是想要对残留的dom结构进行处理必须在destroyed处理,可是react执行完componentWillUnmount以后把事件、数据、dom都所有处理掉了,因此根本不须要其余的钩子函数了
怎么样就算组件被销毁:
当父组件从渲染这个子组件变成不渲染这个子组件的时候,子组件至关于被销毁
调用ReactDOM.unmountComponentAtNode(node) 方法来将某节点中的组件销毁
react中对于事件进行了处理,解决了一些兼容性问题,react事件对象上面挂载着nativeEvent,这个就是原生的事件对象
react对事件对象作了优化,若是不取值的话,值都是null
父组件与子组件通讯
父组件将本身的状态传递给子组件,子组件当作属性来接收,当父组件更改本身状态的时候,子组件接收到的属性就会发生改变
父组件利用ref对子组件作标记,经过调用子组件的方法以更改子组件的状态
子组件与父组件通讯
兄弟组件通讯
在react没有相似vue中的事件总线来解决这个问题,咱们只能借助它们共同的父级组件来实现,将非父子关系装换成多维度的父子关系
复杂的非父子组件通讯在react中很难处理,多组件间的数据共享也很差处理,因此咱们会使用flux、redux来实现这样的功能,解决这个问题
在react中,若是须要 给表单元素设置默认value或者checked,须要设置成defaultValue/defaultChecked,不然设置默认值之后,用户没法更改
在vue中咱们能够将一些通用的、公用的方法放入到某一个纯js对象中,而后,在须要使用改方法的组件中使用mixins配置(值为对象)将该js对象中的方法注入到组件中,这样就能实现代码复用,便于维护
在React中曾经也有这样的api,可是在高版本react中推荐咱们使用es6中的class来建立组件了,这个时候没法使用mixinsapi,因此mixins被废弃了,若是要使用公用代码抽离,咱们可使用模块化
咱们在react中循环列表数据的时候,须要对循环出来的虚拟jsx节点传入上key这个数据,
Keys能够在DOM中的某些元素被增长或删除的时候帮助React识别哪些元素发生了变化。所以你应当给数组中的每个元素赋予一个肯定的标识。
就是若是有多个组件共享一个数据,把这个数据放到共同的父级组件中来管理
在vue中有一个内容分发叫slot,在react中也有实现,就是能够在使用组件的时候,在组件标签内部放入一些不固定的内容,在该组件的模板中,只有{this.props.children}来表示
//App
<Dialog
close={this.ToggleDialogShow} isShow={isDialogShow}
>
<ContentA/>
<ContentA/>
<ContentB/>
</Dialog>
//dialog
<div style={{display:isShow?'block':'none'}} className="dialog">
<Button handler={this.props.close} text="关闭"/>
{this.props.children}//这里就是slot
</div>
复制代码
webpack是一款模块化打包工具,webpack是基于配置的,经过配置一些选项来让webpack执行打包任务。
webpack在打包的时候,依靠依赖关系图,在打包的时候须要告知webpack两个概念:入口和出口
通常状况下,咱们须要使用webpack.config.js进行配置
entry配置项目打包的入口,值能够为单个的字符串执行某一个文件的地址,这个时候该文件就是入口文件,webpack会根据入口文件里各模块间的关系造成依赖关系图,而后根据依赖关系图进行打包
entry:'./src/app.js',
output:{
path:path.join(__dirname,'build'),
filename:'app.js'
}
复制代码
可是有的时候咱们须要的是多入口,咱们就写成数组的形式,数组里的每个字符串地址指向的都是一个独立的入口,webpack会将这些入口的依赖打包
entry:['./src/app.js','./src/vendor.js'],
output:{
path:path.join(__dirname,'build'),
filename:'[name].js'//不肯定名字的时候,这里会打包成main.js
}
复制代码
刚才的两种entry配置都只会打包出一个js文件,可是在某一个应用中咱们可能须要将js根据依赖关系打包成多个js文件,而且在多页面应用中,咱们也确实不可能只使用一个js文件,那么咱们就可使用以下的配置:
entry:{
app:'./src/app.js',
vendor:'./src/vendor.js'
},
output:{
path:path.join(__dirname,'build'),
filename:'[name]_[hash].js'
}
复制代码
这样,由于filename里写成名字是[name],因此会根据entry的配置的键名来为打包出的js文件命名,hash是每次打包的一个随机的hash值,能够用来作版本控制
在这里咱们配置打包输出的一些选项
filename能够肯定打包出来的文件的名字,在里面咱们可使用[name],[hash]这样的占位符
path配置打包出去的文件的路径,须要是绝对路径
在命令行或者终端中执行 webpack --env hello命令,就至关于在打包的时候传入一个参数为hello
在webpack.config.js中能够暴露出一个函数,这个函数就能够接收到env参数,固然函数就能够根据env参数来有选择的返回某一个或多个配置对象
module.exports = (env)=>{
if(env=='production'){
return productionConfig
}
return developmentConfig
}
复制代码
在webpack编译用的是loader,可是有一些loader没法完成的任务,交由插件(plugin)来完成,插件的时候须要在配置项中配置plugins选项,值是数组,能够放入多个插件的使用,而通常的插件都是一个构造器,咱们只需在plugins数组中放入该插件的实例便可,在实例化插件的时候又能够传入options,对插件的使用进行配置
html-webpack-plugin
这个插件能够选择是否依据模板来生成一个打包好的html文件,在里面能够配置、title、template、filename、minify等选项,详情请查阅文档
在这个插件里,咱们可使用jade、hbs、ejs等模板引擎来编译成html,这里举例jade的配置:
npm i jade jade-loader --save-dev
module:{
rules:[
{
test:/\.jade$/,
use:'jade-loader'
}
]
},
plugins:[
new HtmlWebpackPlugin({
// title:'webpack-config-demo',
template:'./src/index.jade',
filename:'index.html'
})
]
复制代码
webpack相辅相成的有一个server功能工具能够提供开发的热更新服务器
npm install webpack-dev-server -g npm install webpack-dev-server -D
第一种启动方式: 直接执行webpack-dev-server,若是有须要配置的选项,在后面跟上参数便可。例如
webpack-dev-server --hot true
复制代码
第二种启动方式:在webpack.config.js中配置devServer的选项,执行webpack-dev-server就ok
devServer:{
port:9000,
contentBase:'./build',
historyApiFallback: true,
open: true,
proxy:{
}
}
复制代码
在webpack中专门有一些东西用来编译文件、处理文件,这些东西就叫loader,loader的使用就是在配置项中,设置modules,在modules中设置rule值为数组,在数组里放入多个匹配规则:
module:{
rules:[
{test:/\.css$/,use:'css-loader'}
],
//before
loaders:[
{test:/\.css$/,loader:'css-loader'}
],
}
复制代码
test为这次匹配要匹配的文件正则规则,use表明要使用的loader
使用url-loader能够将css中引入的图片(背景图)、js中生成的img图片处理一下,生成到打包目录里
视图html-withimg-loader能够将html中img标签引入的img图片打包到打包目录
file-loader
{
test:/\.(png|jpe?g|svg|gif)$/,
// use:'url-loader?limit=1000&name=images/[hash:8].[name].[ext]'
use:[
{
loader:'url-loader',
options:{
limit:1000,
name:'/static/images/assets/[hash:8].[name].[ext]'
}
}
]
},
{
test:/\.html$/,
use:'html-withimg-loader'
}
复制代码
处理css:
cnpm i css-loader style-loader --save-dev
配置:
{ test:/.css$/, use:['style-loader','css-loader'] }
注意。webpack中loader的使用是从后往前的
css-loader能够将引入到js中的css代码给抽离出来,style-loader能够将抽离出来的css代码放入到style标签中
处理sass
{ test:/.scss$/, use:['style-loader','css-loader','sass-loader'] },
将引入项目的css文件、scss文件抽成一个文件,引入到页面中
cnpm i extract-text-webpack-plugin
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
///loader
{
test:/\.css$/,
use:ExtractTextWebpackPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
},
{
test:/\.scss/,
use:ExtractTextWebpackPlugin.extract({
fallback: "style-loader",
use: ["css-loader","sass-loader"]
})
}
///plugin
new ExtractTextWebpackPlugin({
filename:'app.css',
allChunks:true
})
复制代码
处理es6:
须要的依赖: "babel": "^6.23.0", "babel-core": "^6.24.1", "babel-loader": "^7.0.0", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1",
rules: { test:/.js$/, exclude: /node_modules/, loader:'babel-loader', query: { presets: ['es2015','react'] } }
1.建立组件:
使用class来建立组件
class App extends React.Component {
}
2.默认状态的设置
在es6中再也不使用getInitialState来设置默认状态,而是在constructor里面直接给this.state上挂载状态
class App extends Component { constructor(props){ super(props)
this.state={
doing:'吃饭'
}
}
复制代码
}
在es6中,经过给类设置defaultProps属性来设置默认属性
App.defaultProps = { name:'App根组件' }
import PropTypes from 'prop-types';
App.propTypes = { name:PropTypes.string }
5.钩子函数有变化
getDefaultProps、getInitialState没有了
多出了constructor,而这个函数自己是类的构造器,在这里至关于getDefaultProps、getInitialState的结合
npm install creat-react-app -g
create-react-app my-app //生成一个react开发模板在my-app目录 //生成的过程特别缓慢,可使用yarn工具来下载,也就是说先去下载安装yarn :npm install yarn -g
cd my-app
npm install
当咱们使用某些组件的时候,发现,该组件不须要拥有本身的状态,只须要接收到外界传入的属性以后作出相应的反应便可
这样的话,咱们能够利用纯函数的方式将其制做成无状态组件,提升性能
import React from 'react'
const Button = (props)=>{
return <button onClick={props.handler}>我要花钱</button>
}
export default Button
复制代码
在2014年,facebook提出了Flux,Flux 是一种架构思想,专门解决软件的结构问题。它跟MVC 架构是同一类东西,可是更加简单和清晰。
Flux的组成部分:
Flux的流程:
React 只是 DOM 的一个抽象层,并非 Web 应用的完整解决方案。有两个方面,它没涉及。
2014年 Facebook 提出了 Flux 架构的概念,引起了不少的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一块儿,很短期内就成为了最热门的前端架构。
若是你不知道是否须要 Redux,那就是不须要它
只有遇到 React 实在解决不了的问题,你才须要 Redux
简单说,若是你的UI层很是简单,没有不少互动,Redux 就是没必要要的,用了反而增长复杂性。
须要使用redux的项目:
从组件层面考虑,什么样子的须要redux:
redux的设计思想:
redux的流程:
1.store经过reducer建立了初始状态 2.view经过store.getState()获取到了store中保存的state挂载在了本身的状态上 3.用户产生了操做,调用了actions 的方法 4.actions的方法被调用,建立了带有标示性信息的action 5.actions将action经过调用store.dispatch方法发送到了reducer中 6.reducer接收到action并根据标识信息判断以后返回了新的state 7.store的state被reducer更改成新state的时候,store.subscribe方法里的回调函数会执行,此时就能够通知view去从新获取state
注意:flux、redux都不是必须和react搭配使用的,由于flux和redux是完整的架构,在学习react的时候,只是将react的组件做为redux中的视图层去使用了。
reducer必须是一个纯函数:
Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是一样的输入,一定获得一样的输出。
纯函数是函数式编程的概念,必须遵照如下一些约束。
不得改写参数
不能调用系统 I/O 的API
不能调用Date.now()或者Math.random()等不纯的方法,由于每次会获得不同的结果
因为 Reducer 是纯函数,就能够保证一样的State,一定获得一样的 View。但也正由于这一点,Reducer 函数里面不能改变 State,必须返回一个全新的对象,请参考下面的写法。
// State 是一个对象
function reducer(state, action) {
return Object.assign({}, state, { thingToChange });
// 或者
return { ...state, ...newState };
}
// State 是一个数组
function reducer(state, action) {
return [...state, newItem];
}
复制代码
最好把 State 对象设成只读。你无法改变它,要获得新的 State,惟一办法就是生成一个新对象。这样的好处是,任什么时候候,与某个 View 对应的 State 老是一个不变的对象。
咱们能够经过在createStore中传入第二个参数来设置默认的state,可是这种形式只适合于只有一个reducer的时候
划分reducer
由于一个应用中只能有一个大的state,这样的话reducer中的代码将会特别特别的多,那么就可使用combineReducers方法将已经分开的reducer合并到一块儿
注意:
分离reducer的时候,每个reducer维护的状态都应该不一样
经过store.getState获取到的数据也是会安装reducers去划分的
划分多个reducer的时候,默认状态只能建立在reducer中,由于划分reducer的目的,就是为了让每个reducer都去独立管理一部分状态
市场上的react-router的版本有一、二、三、4,1-3的差异不大,使用于16.0.0如下的版本
react-router 4.0 适用于16.0.0以上
在这里使用15.6.1的react。这个版本的react容许使用React.createClass来建立组件,在16以上只能使用class类的方式来建立
渲染根组件的时候,最外层包裹上Router组件,在其上能够设置history属性,值能够是hashHistory||browserHistory
当值为hashHistory的时候,url的变化为hash值的变化,router会去检测hash变化来实现组件的切换
当值为browserHistory的时候,url的变化为path的变化,须要后端进行配置
Router中使用Route组件来描述每一级路由,Route上有path、component属性,表明着当path改变成...的时候,就渲染..组件
在须要切换路由组件的地方,经过this.props.children来表示对应路由组件
在Route中能够屡次嵌套Route来实现多级路由
IndexRoute能够设置该路由中的默认子路由
传入的时候,querystring参数能够在Link里的query中传入和设置,在目标组件中,经过this.props中的,params、routePrams、location等来接收参数
const routeConfig = [
{ path: '/',
component: App,
indexRoute: { component: Home },
childRoutes: [
{ path: 'home', component: Home },
{ path: 'news',
component: News,
childRoutes: [
{ path: 'inside', component: Inside },
{ path: 'outside',component:Outside}
]
},
{ path: 'detail/:id', component: Detail },
{path:'*',component:Home}
]
}
]
ReactDOM.render(
<Router routes={routeConfig} history={hashHistory}></Router>
,document.getElementById('app'))
复制代码
在路由组件中经过this.props.history获取到history对象,利用里面push、replace、go、goBack方法来进行隐式跳转
能够从react-router中引入browserHistory或者hashHistory调用里面的push、replace、go、goBack方法来进行隐式跳转
关于React的UI组件库市场上也有不少,在这里咱们使用蚂蚁金服开发的AntDesign组件库
这是PC端的,移动端的是Antd-Mobile
这个库或者说工具是redux的开发者专门为react建立出来的,为咱们在react中使用redux提供便利
React-Redux 将全部组件分红两大类:UI 组件/木偶组件(presentational component)和容器组件/智能组件(container component)。
UI 组件有如下几个特征。
容器组件的特征偏偏相反。
只要记住一句话就能够了:UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。
你可能会问,若是一个组件既有 UI 又有业务逻辑,那怎么办?回答是,将它拆分红下面的结构:外面是一个容器组件,里面包了一个UI 组件。前者负责与外部的通讯,将数据传给后者,由后者渲染出视图。
React-Redux 规定,全部的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是所有交给它。
使用方法及步骤:
使用Provider组件,包裹在应用的最外层,并为Provider注入store属性,此时,Provider就会将本身的store属性传递给子组件组合中的容器组件
使用connect函数,能够根据一个现有的UI组件生成一个容器组件,且咱们在使用的时候,其实一直在使用的都是容器组件,connect函数执行以后返回一个函数,将返回的函数传入UI组件并执行以后就会生成一个容器组件
connect函数有两个参数:mapStateToProps,mapDispatchToProps
mapStateToProps的做用很简单,就是将redux中的state传递到UI组件的props上,此参数是一个函数,接收到store的state而后再返回一个对象,返回的对象中的属性就会传递到UI组件的属性上
mapStateToProps对store进行了订阅,只要state更改,mapStateToProps会自动执行并获取到最新的state传入到UI组件的属性上
mapDispatchToprops 函数,接收到dispatch参数,其实就是store.dispatch,返回的对象中设置的方法可使用到dispatch,且能传入到UI组件的属性上
那么,有了mapDistpatchToProps以后,咱们就不须要actions了吗?
咱们须要将一些复杂的业务逻辑,或者说异步的业务逻辑抽离出来放入到actions里面去,也就是后所mapDispatchToProps里本身建立的只是一些简单的方法就能够了
第一次使用react-redux等工具的作法
建立了actionCreator,专门生成action,又设置 了actions,在actions里放一些异步的、复杂的操做以后,调用actionCreator生成action再dispatch到reducer
其实咱们上面建立actions的目的,就是由于ActionCreator不能作复杂的动做,其实咱们可使用redux-thunk来对reducer建立中间件,让actionCreator的方法能返回一个函数,这个函数就能够接收到dispatch,且作出异步操做以后dispatch出action,也就是说,咱们不须要再建立actions来分离异步复杂操做,并且直接能够在ActionCreator里写异步方法
步骤:
import {createStore,applyMiddleware} from 'redux'
import reducer from './reducer'
import thunk from 'redux-thunk'
const store = createStore(reducer,applyMiddleware(thunk))
export default store
复制代码
const actions_thunk = {
clearCart(){
//作异步操做以后生成action且dispatch
return (dispatch)=>{
setTimeout(function(){
localStorage.removeItem('cars')
let action = change_cars([])
dispatch(action)
},500)
}
}
}
复制代码
import {bindActionCreators} from 'redux'
import actions_thunk from '../../redux/ActionCreators/actions_thunk'
export default connect(state=>state,dispatch=>{
return {
actions_thunk:bindActionCreators(actions_thunk,dispatch)
}
})(ClearCar)
///
button onClick={this.props.actions_thunk.clearCart}
复制代码
其实,如今有这样的流行作法:
将全部的数据都交由redux管理,这样的话,咱们的组件在UI层的逻辑就更纯粹了,并且能够作到数据缓存,好比,A组件获取了数据放入到redux中,当A组件被切换掉以后从新切换回来的时候,数据依然在redux中能够找到,也就是说直接取出来用就ok,不须要从新获取
<Son ref="son"></Son>
//
this.refs.son
复制代码
<Son ref={(el)=>{this.son = el}}></Son>
//
this.son
复制代码
声明式设计、高效、灵活、(双向数据流)
动画可使用哪一个第三方插件实现:
(ReactTransitionGroup)/animate.css/transitionTranslate/redux-thunk
ReactRouter中,路由的onLeave应该写在哪里:
路由对应的组件中、父组件中、(路由组件中)、最外层大组件中
react-redux中的connect方法的返回值是一个:
对象、(容器组件)、UI组件、数组
react中常提到的中间件的概念,指的是:
react中间件、flux中间件、(redux中间件)、react-redux中间件
redux设计的三大原则:
(store惟一/惟一数据源、 state只读、 reducer是纯函数) reducer只读
哪些不是react-router的组件:
(Provider)、Route、Router、(MapStateToProps)
下面哪些方法可使componentWillUpdate执行
属性更改或者状态更改
请简述对虚拟dom的理解,为何使用虚拟DOM能够极大的提高react的性能
虚拟dom是真实dom的js对象映射,使用虚拟dom,避免对原生dom的建立和比对,取而代之的建立和比对的是js对象
原生dom的建立和比对是很是消耗性能的,而js对象的对比和建立对性能开销很小,从这种方式来提供应用的性能
请说明在react中ref的做用,并写出使用ref的两种方式,说明哪种是官方推荐的
ref可使咱们在react对dom或者子组件作出标记并获取:
//this.refs.son <Son ref={(el)=>{this.son = el}}>//this.son(官方推荐)
说明react中,父子组件项目传值的方式,并说明在大型项目中为何要引入flux或者redux这种开发架构 父组件将本身的状态当成属性传递给子组件 父组件将本身的方法传递给子组件,子组件在调用的时候传参 父组件经过ref获取到子组件,调用子组件的方法传参
react是一款视图层的轻量级前端框架,大量的非父子组件通讯、状态共享会致使整个项目数据复杂,难以维护,因此react不适合处理大量的数据通讯,为了解决这个问题,引入了FLUX、REDUX这样的数据架构,react结合FLUX或者redux才能实现比较复杂的前端项目
在react中,列表循环尽可能不要使用index做为key值,这和diff算法有关系,请简述diff算法中key值有什么做用,为何key中使用index值会下降代码性能
key值是diff算法中对比两个虚拟dom的最重要参考,决定了哪些列表中的组件能够复用,若是使用index做为key中,列表数据改变后,会致使同一个dom元素的key中发送改变,原本能够复用的组件必须从新建立,下降页面性能,除非列表不须要改变,通常状况不使用index做为key值
请列举你所了解的react中的性能优化
请说明react中引入redux-thunk、redux-promise这两种中间件能够解决什么样的问题
一般状况下,action只是一个对象,不能包含异步操做,这致使了不少建立action的逻辑只能写在组件中,代码量较多也不便于复用,同时对该部分代码测试的时候也比较困难,组件的业务逻辑也不清晰,使用中间件了以后,能够经过actionCreator异步编写action,这样代码就会拆分到actionCreator中,可维护性大大提升,能够方便于测试、复用,同时actionCreator还集成了异步操做中不一样的action派发机制,减小编码过程当中的代码量
画图说明redux的架构,写出redux中经常使用的函数
dispatch , subscribe,combineReducers,getState,createStore
简述react-redux结合react-router在项目中的使用方式
下列说法错误的是:
(React是一款专一于数据层的前端框架)、react中须要调用setState方法来重置状态、react中的虚拟dom能够提高框架自身的性能、(react是一款符合MVVM设计思想的前端框架)
关于前端组件化说法错误的是:
前端组件使得复杂的代码得以被更好的划分、组件化的代码设计方式增长了代码的可复用性、(在拆分组件的时候将组件拆分的越小越细越好)、组件化开发是一种设计思想,不是react独享的
在react中,异步获取ajax数据通常放在那个生命周期函数:componentWillMount
使用es6定义组件,能够在那个生命周期钩子函数里使用this.state=state 对state进行赋值。而不须要调用this.setState方法:constructor
在redux中,重要的组成部分不包括:
store、action、reducer、(dispatcher)
webpack中html-webpack-plugin插件能够完成的任务是:
(在打包输出目录中自动生成index.html)、(向打包目录中的index.html文件内插入打包生成的js文件引用)、将js源码中引用的css代码抽离ちゅ单独的css文件并放置到打包输出目录、(像打包输出目录中的index.html文件插入打包生成的css引用)
关于jsx,说明正确的是:(ad)
a:jsx中能够经过{}来使用js的表达式,b:jsx中能够经过{}来使用js的代码,c:jsx中可使用style={color:'red'}来设置元素的样式、d:jsx代码会被解析成一个js对象
react组件被挂载到页面 的时候,被执行的生命周期函数包括:(ab)
a: componentWillMount,b:render,c:componentDidUpdate,d:shouldComponentUpdate
在自定义的react组件中,哪些生命周期函数能够不写(acd)
a: constructor b:render c:componentWillMount d:componentWillUnmount
说法正确的是:(ab)
a: 父组件经过属性的方式给子组件传值,b:子组件经过props接收父组件的值,c:state中和页面无关的属性发送变化时,render不会执行,d:shouldComponentUpdate函数的两个参数分别是当前的state和当前的props
在react组件中,当(props或者state)发送变化的时候,会致使render生命周期函数从新执行
使用react-redux时,页面上的组件须要被拆分红(容器)组件和(UI)组件,经过使用(connect/mapStateToProps)方法,能够把store中固定state内容映射到组件上
使用ES6建立react组件的方式是(class ... extends React.Component),ES5建立组件的方法是(React.createClass),建立无状态组件的方式是(function(props){})
react中,ref属性的做用是(获取jsx中元素的真实dom节点或子组件)
es5语法建立react组件比es6多了两个生命周期函数(getDefaultProps/getInitialState)
请简述react-router中hashHistory和browserHistory的区别:
这是react-router中设置监听url路径变化的两种方式,hashHistory在切换路由的时候会在url上跟着哈希值,browserHistory经过判断path变化才切换路由,且path变化的时候后端能够接收到请求,须要后端配置忽略掉
请画图说明flux中的单向数据流
用户访问View-》view发出用户的action-》dispatcher收到action要求store进行更新-》store更改后,触发一个事件-》view接收到该事件的触发,更新页面
简述react-redux的用法
建立store、reducer,经过provider将store传递给各个子组件,建立ui组件,生成容器组件,利用connect将store和容器组件链接
说明redux设计和使用的三大原则:
惟一数据源、保持状态只读、数据改变只能经过纯函数完成
说明redux-thunk的使用方法:
npm install redux-thunk -S
//store import {createStore,applyMiddleware} from 'redux' import thunk from 'redux-thunk'
import reducer from './reducer'
const store = createStore(reducer,applyMiddleware(thunk))
//actionCreator
const actionCreator = { handlerChange(){ return (dispatch)=>{
...
}
}
复制代码
}