#React框架的学习#
React的起源和发展 起初facebook在建设instagram(图片分享)的时候因为牵扯到一个东西叫数据流,为了处理数据流并且还要考虑好性能方面的问题,Facebook开始对市场上的各种前端MVC框架去进行一个研究,然而并没有看上眼的,于是Facebook觉得,还是自己开发一个才是最棒的,那么他们决定抛开很多所谓的“最佳实践”,重新思考前端界面的构建方式,他们就自己开发了一套,果然大牛创造力还是很强大的。
#React#出发点
基于HTML的前端界面开发正变得越来越复杂,其本质问题基本都可以归结于如何将来自于服务器端或者用户输入的动态数据高效的反映到复杂的用户界面上。而来自Facebook的React框架正是完全面向此问题的一个解决方案,按官网描述,其出发点为:用于开发数据不断变化的大型应用程序(Building large applications with data that changes over time)。相比传统型的前端开发,React开辟了一个相当另类的途径,实现了前端界面的高性能高效率开发。
# React与传统MVC的关系#
一个 轻量级的视图层框架! React不是一个完整的MVC框架,最多可以认为是MVC中的V(View),甚至React并不非常认可MVC开发模式!!!
# React高性能的体现#
虚拟DOM!!!React高性能的原理:在Web开发中我们总需要将变化的数据实时反应到UI上,这时就需要对DOM进行操作。而复杂或频繁的DOM操作通常是性能瓶颈产生的原因(如何进行高性能的复杂DOM操作通常是衡量一个前端开发人员技能的重要指标)。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不发生任何变化,而如果通过手动控制,这种逻辑通常是极其复杂的。尽管每一次都需要构造完整的虚拟DOM树,但是因为虚拟DOM是内存数据,性能是极高的,部而对实际DOM进行操作的仅仅是Diff分,因而能达到提高性能的目的。这样,在保证性能的同时,开发者将不再需要关注某个数据的变化如何更新到一个或多个具体的DOM元素,而只需要关心在任意一个数据状态下,整个界面是如何Render的。
# React的特点和优势#
1. 虚拟DOM:我们以前操作dom的方式是通过document.getElementById()或其他类似的方式,这样的过程实际上是先去读取html的dom结构,将结构转换成变量,再进行操作。而React定义了一套变量形式的dom模型,一切操作和换算直接在变量中,这样减少了操作真实dom,性能相当的高,但和主流MVC框架有本质的区别,并不和dom打交道。2. 组件系统:React最核心的思想是将页面中任何一个区域或者元素都可以看做一个组件 component,那么什么是组件呢?组件指的就是同时包含了html、css、js、image元素的聚合体,使用React开发的核心就是将页面拆分成若干个组件,并且react一个组件中同时耦合了css、js、image,这种模式整个颠覆了过去的传统的方式。3. 单向数据流:其实React的核心内容就是数据绑定,所谓数据绑定指的是只要将一些服务端的数据和前端页面绑定好,开发者只关注实现业务就行了。4. JSX 语法:在vue中,我们使用render函数来构建组件的dom结构性能较高,因为省去了查找和编译模板的过程,但是在render中利用createElement创建结构的时候代码可读性较低,较为复杂,此时可以利用jsx语法来在render中创建dom,解决这个问题,但是前提是需要使用工具来编译jsx。
#创建第一个组件#
React开发需要引入多个依赖文件:react.js、react-dom.js,分别又有开发版本和生成版本在这里一开始,我们先学习es5的组件写法,React.createClass,需要引入的是15+react.js中有React对象,帮助我们创建组件等功能react-dom.js中有ReactDOM对象,渲染组件的虚拟dom为真实dom的爆发功能,在编写react代码的时候会大量的使用到jsx代码,但是需要编译:
1. 浏览器端编译,通过引入browser、babel等对引入的script内的代码做编译
2. 利用webpack等开发环境进行编译,将编译好的文件引入到应用中
//创建组件
var Hello = React.createClass({
render:function () {
return (asdasd) //jsx语法
}
})
//render函数和Vue组件里的render完全一样,在vue组件中可以不用编写render函数,这个时候可以使用template模板来编写组件的虚拟dom结构,然后vue组件会自动讲模板compile成虚拟dom结构放入到render中执行,但是react需要编写render函数
//利用ReactDOM对象的render方法将组件渲染到某个节点里 ReactDOM.render(,document.getElementById("app"))
组件是通过React.createClass创建的(ES5),在ES6中直接通过class关键字来创建组件其实就是一个构造器,每次使用组件都相当于在实例化组件react的组件必须使用render函数来创建组件的虚拟dom结构组件需要使用ReactDOM.render方法将其挂载在某一个节点上组件的首字母必须大写
# JSX语法糖#
JSX是一种语法,全称:javascript xmlJSX语法不是必须使用的,但是因为使用了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的语法,例如:最外层必须有根节点、标签必须闭合jsx借鉴xml的语法而不是html的语法原因:xml要比html严谨,编译更方便。
#组件dom添加样式#
在react里表达式的符号是 "{ }",作用和vue的表达式作用是一样的想给虚拟dom添加行内样式,需要使用表达式传入样式对象的方式来实现;
行内样式需要写入一个样式对象,而这个样式对象的位置可以放在很多地方,例如React.createClass的配置项中、render函数里、组件原型上、外链js文件中。React推荐我们使用行内样式,因为react觉得每一个组件都是一个独立的整体其实我们大多数情况下还是大量的在为元素添加类名、id以使用某些样式,但是需要注意的是,class需要写成className(因为毕竟是在写类js代码,会收到js规则的现在,而class是关键字)
# React Event#
在react中,我们想要给组件的dom添加事件的话,也是需要在行内添加的方式,事件名字需要写成小驼峰的方式,值利用表达式传入一个函数即可注意,在没有渲染的时候,页面中没有真实dom,所以是获取不到dom的,给虚拟dom结构中的节点添加样式。在行内添加,写成驼峰形式,值是一个函数名,需要用{}包裹。
# 组件嵌套#
将一个组件渲染到某一个节点里的时候,会将这个节点里原有内容覆盖。组件嵌套的方式就是将子组件写入到父组件的模板中去,且react没有Vue中的内容分发机制(slot),所以我们在一个组件的模板中只能看到父子关系。
无状态组件:var Person =function(){return (lilei)}ReactDOM.render(,app)>注意:react中jsx里的注释要写成{/* */}的方式。
# React中的数据承载-Props/State#
React也是基于数据驱动(声明式)的框架,组件中必然需要承载一些数据,在react中起到这个作用的是属性和状态(props & state)
1. 属性(props) 在组件外部传入,或者内部设置,组件内部通过this.props获得
2. 状态(state) 在组件内部设置或者更改,组件内部通过this.state获得
# 属性(props)#
属性一般是外部传入的,组件内部也可以通过一些方式来初始化的设置,属性不能被组件自己更改属性是描述性质、特点的,组件自己不能随意更改使组件拥有属性的方式:
1. 在装载(mount)组件的时候给组件传入传入数据的时候,除了字符串类型,其他的都应该包上表达式,但是为了规整,所有的数据传递,最好都包上{}
var Gouzi = React.createClass({
render(){
console.log(this)
return (
我的名字:{this.props.name}
我的性别:{this.props.sex}
我的年龄:{this.props.age}
我的父亲是:{this.props.father}
) }})
let info = {sex:'male', father:'狗爸'}
ReactDOM.render(,app)
2. 父组件给子组件传入,父组件在嵌套子组件的时候为子组件传入,传入的方式和上面的方式一样//父组件的render函数render(){ return (
父组件)}
3. 子组件自己设置子组件可以通过getDefaultProps来设置默认的属性。getDefaultProps的值是函数,这个函数会返回一个对象,我们在这里对象里为组件设置默认属性这种方式,设置的属性优先级低,会被外部传入的属性值所覆盖。
根据属性或状态,我们可以在render中的表达式里做一些逻辑判断,可以使用||、三元表达式、子执行函数等等
getName(){return this.props.name || '野狗子'},
render:function () {
let {name} = this.props
return (
我是子组件-{this.props.name || '野狗子'}
我是子组件-{this.props.name?this.props.name:'野狗子'}
我是子组件-{this.getName()}
我是子组件-{(function (obj) return obj.props.name || '野狗子' })(this)})}
#状态(state)#
状态就是组件描述某种显示情况的数据,由组件自己设置和更改,也就是说由组件自己维护,使用状态的目的就是为了在不同的状态下使组件的显示不同(自己管理)。在组件中只能通过getInitialState的钩子函数来给组件挂载初始状态,在组件内部通过this.state获取。this.props和this.state是纯js对象,在vue中,$data属性是利用Object.defineProperty处理过的,更改$data的数据的时候会触发数据的getter和setter,但是react中没有做这样的处理,如果直接更改的话,react是无法得知的,所以,需要使用特殊的更改状态的方法:setState(params)。在setState中传入一个对象,就会将组件的状态中键值对的部分更改,还可以传入一个函数,这个回调函数必须返回像上面方式一样的一个对象,函数可以接收prevState和props
#属性和状态的对比相似点#
都是纯js对象,都会触发render更新,都具有确定性(状态/属性相同,结果相同)
不同点:
1. 属性能从父组件获取,状态不能
2. 属性可以由父组件修改,状态不能
3. 属性能在内部设置默认值 ,状态也可以
4. 属性不在组件内部修改 ,状态要改
5. 属性能设置子组件初始值 ,状态不可以
6. 属性可以修改子组件的值,状态不可以,状态只和自己相关,由自己维护。属性不要自己修改,可以从父组件获取,也可以给子组件设置组件在运行时自己需要修改的数据,其实就是状态而已
# 组件的生命周期#
react中组件也有生命周期,也就是说也有很多钩子函数供我们使用。组件是一个构造器,每一次使用组件都相当于在实例化组件,在这个时候,组件就会经历一次生命周期,从实例化实例开始到这个实例销毁的时候,都是一次完整的生命周期组件的生命周期,我们会分为三个阶段,初始化、运行中、销毁。
#初始化阶段#
1. 实例化组件之后,组件的getDefaultProps钩子函数会执行,这个钩子函数的目的是为组件的实例挂载默认的属性。这个钩子函数只会执行一次,也就是说,只在第一次实例化的时候执行,创建出所有实例共享的默认属性,后面再实例化的时候,不会执行getDefaultProps,直接使用已有的共享的默认属性。理论上来说,写成函数返回对象的方式,是为了防止实例共享,但是react专门为了让实例共享,只能让这个函数只执行一次,组件间共享默认属性会减少内存空间的浪费,而且也不需要担心某一个实例更改属性后其他的实例也会更改的问题,因为组件不能自己更改属性,而且默认属性的优先级低。
2. 执行getInitialState为实例挂载初始状态,且每次实例化都会执行,也就是说,每一个组件实例都拥有自己独立的状态。
3. 执行componentWillMount,相当于Vue里的created+beforeMount,这里是在渲染之前最后一次更改数据的机会,在这里更改的话是不会触发render的重新执行,在这里多做一些初始数据的获取
4. 执行render,渲染dom
5. 执行componentDidMount ,相当于Vue里的mounted,多用于操作真实dom
#运行中阶段#
当组件mount到页面中之后,就进入了运行中阶段,在这里有5个钩子函数,但是这5个函数只有在数据(属性、状态)发送改变的时候才会执行
1. componentWillReceiveProps当父组件给子组件传入的属性改变的时候,子组件的这个函数才会执行,当执行的时候,函数接收的参数是子组件接收到的新参数,这个时候,新参数还没有同步到this.props上,多用于判断新属性和原有属性的变化后更改组件的状态。
2. 接下来就会执行shouldComponentUpdate,这个函数的作用: 当属性或状态发送改变后控制组件是否要更新,提高性能,返回true就更新,否则不更新,默认返回true 接收nextProp、nextState,根据新属性状态和原属性状态作出对比、判断后控制是否更新。
3. componentWillUpdate,在这里,组件马上就要重新render了,多做一些准备工作,千万千万,不要在这里修改状态,否则会死循环 相当于Vue中的beforeUpdate。
4. render,重新渲染dom。
5. componentDidUpdate,在这里,新的dom结构已经诞生了,相当于Vue里的updated。
#销毁阶段#
当组件被销毁之前的一刹那,会触发componentWillUnmount,临死前的挣扎相当于Vue里的beforeDestroy,所以说一般会做一些擦屁股的事情为什么Vue中有destroyed,而react却没有componentDidUnmount。Vue在调用$destroy方法的时候就会执行beforeDestroy,然后组件被销毁,这个时候组件的dom结构还存在于页面结构中,也就说如果想要对残留的dom结构进行处理必须在destroyed处理,但是react执行完componentWillUnmount之后把事件、数据、dom都全部处理掉了,所以根本不需要其他的钩子函数了。
怎么样就算组件被销毁:
1. 当父组件从渲染这个子组件变成不渲染这个子组件的时候,子组件相当于被销毁
2. 调用ReactDOM.unmountComponentAtNode(node) 方法来将某节点中的组件销毁
#React中的事件对象#
React中对于事件进行了处理,解决了一些兼容性问题,React事件对象上面挂载着nativeEvent,这个就是原生的事件对象。React对事件对象做了优化,如果不取值的话,值都是null。
# React中组件通信方式#
父组件与子组件通信:
1. 父组件将自己的状态传递给子组件,子组件当做属性来接收,当父组件更改自己状态的时候,子组件接收到的属性就会发生改变
2. 父组件利用ref对子组件做标记,通过调用子组件的方法以更改子组件的状态。
子组件与父组件通信:
1. 父组件将自己的某个方法传递给子组件,在方法里可以做任意操作,比如可以更改状态,子组件通过this.props接收到父组件的方法后调用。兄弟组件通信在react没有类似vue中的事件总线来解决这个问题,我们只能借助它们共同的父级组件来实现,将非父子关系装换成多维度的父子关系,复杂的非父子组件通信在react中很难处理,多组件间的数据共享也不好处理,所以我们会使用flux、redux来实现这样的功能,解决这个问题。
# React中表单元素默认值#
在React中,如果需要给表单元素设置默认value或者checked,需要设置成defaultValue/defaultChecked,设置默认值以后,用户无法更改。
#React中的mixins#
在vue中我们可以将一些通用的、公用的方法放入到某一个纯js对象中,然后,在需要使用改方法的组件中使用mixins配置(值为对象)将该js对象中的方法注入到组件中,这样就能实现代码复用,便于维护。在React中曾经也有这样的API,但是在高版本react中推荐我们使用es6中的class来创建组件了,这个时候无法使用mixins API,所以mixins被废弃了。如果要使用公用代码抽离,我们可以使用模块化
#React-keys#
我们在react中循环列表数据的时候,需要对循环出来的虚拟jsx节点传入上key这个数据,Keys可以在DOM中的某些元素被增加或删除的时候帮助React识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。
# 状态提升#
状态提升就是如果有多个组件共享一个数据,把这个数据放到共同的父级组件中来管理
# 组合#
在vue中有一个内容分发叫slot,在react中也有实现,就是可以在使用组件的时候,在组件标签内部放入一些不固定的内容,在该组件的模板中,只有{this.props.children}来表示。{this.props.children}//这里就是slot
# webpack#
webpack是一款模块化打包工具,webpack是基于配置的,通过配置一些选项来让webpack执行打包任务。webpack在打包的时候,依靠依赖关系图,在打包的时候需要告知webpack两个概念:入口和出口。一般情况下,我们需要使用webpack.config.js进行配置。
#entryentry#
配置项目打包的入口,值可以为单个的字符串执行某一个文件的地址,这个时候该文件就是入口文件,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值,可以用来做版本控制。
# output #
在这里我们配置打包输出的一些选项filename可以确定打包出来的文件的名字,在里面我们可以使用[name],[hash]这样的占位符,path配置打包出去的文件的路径,需要是绝对路径。
# env#
在命令行或者终端中执行 webpack --env hello 命令,就相当于在打包的时候传入一个参数为hello,在webpack.config.js中可以暴露出一个函数,这个函数就可以接收到env参数,当然函数就可以根据env参数来有选择的返回某一个或多个配置对象。
module.exports = (env)=>{ if(env=='production'){ return productionConfig} return developmentConfig}
# plugins#
webpack编译用的是loader,但是有一些loader无法完成的任务,交由插件(plugin)来完成,插件的时候需要在配置项中配置plugins选项,值是数组,可以放入多个插件的使用,而一般的插件都是一个构造器,我们只需在plugins数组中放入该插件的实例即可,在实例化插件的时候又可以传入options,对插件的使用进行配置,html-webpack-plugin这个插件可以选择是否依据模板来生成一个打包好的html文件,在里面可以配置、title、template、filename、minify等选项。详情可参考这篇文章(https://segmentfault.com/a/1190000007294861)
在这个插件里,我们可以使用jade、hbs、ejs等模板引擎来编译成html,这里举例jade的配置:可参考(https://segmentfault.com/a/1190000000357534)
npm i jade jade-loader --save-
devmodule:{ rules:[ {test:/\.jade$/,use:'jade-loader' } ]},
plugins:[ new HtmlWebpackPlugin({ // title:'webpack-config-demo', template:'./src/index.jade', filename:'index.html' })]
# webpack-dev-server#
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中配置dev-Server的选项,执行webpack-dev-server就ok
devServer:{port:端口号, contentBase:'./build', historyApiFallback: true, open: true, proxy:{ }}```
#loader#
在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
处理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文件抽成一个文件,引入到页面中。
#ES6#
ES6中的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:'吃饭'}}}
3. 默认属性的设置在es6中,通过给类设置defaultProps属性来设置默认属性App.defaultProps = {name:'App根组件'}
4. 做属性传参验证import PropTypes from 'prop-types';App.propTypes = {name:PropTypes.string}
5.钩子函数有变化getDefaultProps、getInitialState没有了多出了constructor,而这个函数本身是类的构造器,在这里相当于getDefaultProps、getInitialState的结合
#create-react-app#
create-react-app 脚手架:
npm install create-react-app -g
create-react-app my-app
生成一个react开发模板在my-app目录,生成的过程特别缓慢,可以使用yarn工具来下载,也就是说先去下载安装yarn :
npm install yarn -g
cd my-app 二次配置的时候需要找到node_modules下的react-scripts文件进行配置,但是我们可以执行cnpm run eject可以将配置抽出,方便开发配置。npm install
#组件#
无状态组件、纯组件当我们使用某些组件的时候,发现,该组件不需要拥有自己的状态,只需要接收到外界传入的属性之后做出相应的反应即可这样的话,我们可以利用纯函数的方式将其制作成无状态组件,提高性能
import React from 'react';
const Button = (props)=>{return我要花钱}
export default Button
#Flux#
在2014年,facebook提出了Flux,Flux 是一种架构思想,专门解决软件的结构问题。它跟MVC 架构是同一类东西,但是更加简单和清晰。其实Flux 在react里的应用就类似于vue中的vuex的作用,但是在vue中,vue是完整的mvvm框架,而vuex只是一个全局的插件.react只是一个视图层的框架,在flux是一个架构思想,我们在做项目的时候使用flux架构的话要比单纯使用react要简单很多,这个时候,react在整个Flux 架构中担任某一个角色的react在这里只是充当了Flux 架构体系中的view层。Flux的组成部分:
* View: 视图层* ActionCreator(动作创造者):视图层发出的消息(比如mouseClick)
* Dispatcher(派发器):用来接收Actions、执行回调函数* Store(数据层):用来存放应用的状态,一旦发生变动,就提醒View要更新页面
Flux的流程:
1. 组件获取到store中保存的数据挂载在自己的状态上
2. 用户产生了操作,调用actions的方法
3. actions接收到了用户的操作,进行一系列的逻辑代码、异步操作
4. 然后actions会创建出对应的action,action带有标识性的属性
5. actions调用dispatcher的dispatch方法将action传递给dispatcher
6. dispatcher接收到action并根据标识信息判断之后,调用store的更改数据的方法
7. store的方法被调用后,更改状态,并触发自己的某一个事件
8. store更改状态后事件被触发,该事件的处理程序会通知view去获取最新的数据
#redux#
React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。有两个方面,它没涉及:
1. 代码结构: 组件之间的通信2014年 Facebook 提出了 Flux 架构的概念,引发了很多的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。如果你不知道是否需要 Redux,那就是不需要它只有遇到 React 实在解决不了的问题,你才需要 Redux简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。
2. 用户的使用方式非常简单: 用户之间没有协作,不需要与服务器大量交互,也没有使用 WebSocket。 视图层(View)只从单一来源获取数据需要使用redux的项目: 用户的使用方式复杂,不同身份的用户有不同的使用方式(比如普通用户和管理员)多个用户之间可以协作,与服务器大量交互,或者使用了WebSocket。View要从多个来源获取数据从组件层面考虑,什么样子的需要redux:某个组件的状态,需要共享, 某个状态需要在任何地方都可以拿到, 一个组件需要改变全局状态, 一个组件需要改变另一个组件的状态。
redux的设计思想:
1. Web 应用是一个状态机,视图与状态是一一对应的。
2. 所有的状态,保存在一个对象里面(唯一数据源)。
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合并到一起注意:
1. 分离reducer的时候,每一个reducer维护的状态都应该不同
2. 通过store.getState获取到的数据也是会通过reducers去划分的
3. 划分多个reducer的时候,默认状态只能创建在reducer中,因为划分reducer的目的,就是为了让每一个reducer都去独立管理一部分状态
#React-router#
市场上的react-router的版本有1、2、3、4,1-3的差别不大,使用于16.0.0以下的版本,react-router 4.0 适用于16.0.0以上。在这里使用15.6.1的react。这个版本的react允许使用React.createClass来创建组件,在16以上只能使用class类的方式来创建:
1. 渲染根组件的时候,最外层包裹上Router组件,在其上可以设置history属性,值可以是hashHistory||browserHistory ,当值为hashHistory的时候,url的变化为hash值的变化,router会去检测hash变化来实现组件的切换,当值为browserHistory的时候,url的变化为path的变化,需要后端进行配置
2. Router中使用Route组件来描述每一级路由,Route上有path、component属性,代表着当path改变成...的时候,就渲染..组件
3. 在需要切换路由组件的地方,通过this.props.children来表示对应路由组件
4. 在Route中可以多次嵌套Route来实现多级路由
5. IndexRoute可以设置该路由中的默认子路由
6. IndexRedirect可以设置在进入该路由之后马上跳转到哪里
7. 使用Redirect组件可以做到从一个路由马上重定向到其他路由,利用这样的属性,当我们form设置为'*'的时候,就可以将匹配不到的路由重定向到某路由下
8. 可以在配置Route的时候给path里加入/:param 才表示此路由需要参数传入的时候,querystring参数可以在Link里的query中传入和设置,在目标组件中,通过this.props中的,params、routePrams、location等来接收参数
9. 可以通过过Router传入routes参数,值为数组,来设置路由配置:
10. 编程式导航, 在路由组件中通过this.props.history获取到history对象,利用里面push、replace、go、goBack方法来进行隐式跳转。可以从react-router中引入browserHistory或者hashHistory调用里面的push、replace、go、goBack方法来进行隐式跳转
11. 可以通过在路由配置上设置 onLeave和onEnter路由钩子来监听路由的变化
#UI组件库#
关于React的UI组件库市场上也有很多,我们使用蚂蚁金服开发的AntDesign组件库,这是PC端的,移动端的是Antd-Mobile
#React-redux#
React-redux这个库或者说工具是redux的开发者专门为react创建出来的,为我们在react中使用redux提供便利起到的是桥梁的作用,能将react和redux更好的连接在一起React-Redux 将所有组件分成两大类:UI 组件/木偶组件(presentational component)和容器组件/智能组件(container component)。UI 组件有以下几个特征:
1只负责 UI 的呈现,不带有任何业务逻辑, 没有状态(即不使用this.state这个变量)*,所有数据都由参数(this.props)提供*,不使用任何 Redux 的 API。
2.容器组件的特征恰恰相反。负责管理数据和业务逻辑,不负责 UI 的呈现,带有内部状态* 使用 Redux 的 API只要记住一句话就可以了:UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。你可能会问,如果一个组件既有 UI 又有业务逻辑,那怎么办?
回答是:将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI 组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图。React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。使用方法及步骤:
1. 使用Provider组件,包裹在应用的最外层,并为Provider注入store属性,此时,Provider就会将自己的store属性传递给子组件组合中的容器组件2. 使用connect函数,可以根据一个现有的UI组件生成一个容器组件,且我们在使用的时候,其实一直在使用的都是容器组件,connect函数执行之后返回一个函数,将返回的函数传入UI组件并执行之后就会生成一个容器组件
3. 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里写异步方法步骤:
1. 对store中的reducer使用redux-thunk
import {createStore,applyMiddleware} from 'redux';
import reducer from './reducer';
import thunk from 'redux-thunk';
const store = createStore(reducer,applyMiddleware(thunk))
export default store;
2. 在ActionCreator的方法中返回方法来做异步处理
3. 可以将actionCreator的方法利用bindActionCreator放入到mapDispatchToProps中
现在有这样的流行做法:将所有的数据都交由redux管理,这样的话,我们的组件在UI层的逻辑就更纯粹了,而且可以做到数据缓存,比如,A组件获取了数据放入到redux中,当A组件被切换掉之后重新切换回来的时候,数据依然在redux中可以找到,也就是说直接取出来用就ok,不需要重新获取。
#React 扩展#
更新中。。。。。