#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 擴展#?
更新中。。。。。