React 起源於 Facebook 的內部項目,由於該公司對市場上全部 JavaScript MVC 框架,都不滿意,就決定本身寫一套,用來架設 Instagram 的網站。作出來之後,發現這套東西很好用,就在2013年5月開源了。javascript
A JAVASCRIPT LIBRARY FOR BUILDING USER INTERFACEScss
diff算法
和 虛擬DOM
實現視圖的高效更新> JSX --TO--> EveryThing - JSX --> HTML - JSX --> native ios或android中的組件(XML) - JSX --> VR - JSX --> 物聯網
組件化
開發方式,符合現代Web開發的趨勢React將DOM抽象爲虛擬DOM,虛擬DOM其實就是用一個對象來描述DOM,經過對比先後兩個對象的差別,最終只把變化的部分從新渲染,提升渲染的效率爲何用虛擬dom,當dom反生更改時須要遍歷 而原生dom可遍歷屬性多大231個 且大部分與渲染無關 更新頁面代價太大html
當你使用React的時候,在某個時間點 render() 函數建立了一棵React元素樹,
在下一個state或者props更新的時候,render() 函數將建立一棵新的React元素樹,
React將對比這兩棵樹的不一樣之處,計算出如何高效的更新UI(只更新變化的地方)
<!-- 瞭解: 有一些解決將一棵樹轉換爲另外一棵樹的最小操做數算法問題的通用方案。然而,樹中元素個數爲n,最早進的算法 的時間複雜度爲O(n3) 。 若是直接使用這個算法,在React中展現1000個元素則須要進行10億次的比較。這操做太過昂貴,相反,React基於兩點假設,實現了一個O(n)算法,提高性能: -->
React中有兩種假定:vue
// 舊樹 <div> <Counter /> </div> // 新樹 <span> <Counter /> </span> 執行過程:destory Counter -> insert Counter
// 舊 <div className="before" title="stuff" /> // 新 <div className="after" title="stuff" /> 只更新:className 屬性 // 舊 <div style={{color: 'red', fontWeight: 'bold'}} /> // 新 <div style={{color: 'green', fontWeight: 'bold'}} /> 只更新:color屬性
// 舊 <ul> <li>first</li> <li>second</li> </ul> // 新 <ul> <li>first</li> <li>second</li> <li>third</li> </ul> 執行過程: React會匹配新舊兩個<li>first</li>,匹配兩個<li>second</li>,而後添加 <li>third</li> tree
// 舊 <ul> <li>Duke</li> <li>Villanova</li> </ul> // 新 <ul> <li>Connecticut</li> <li>Duke</li> <li>Villanova</li> </ul> 在沒有key屬性時執行過程: React將改變每個子刪除從新建立,而非保持 <li>Duke</li> 和 <li>Villanova</li> 不變
爲了解決以上問題,React提供了一個 key 屬性。當子節點帶有key屬性,React會經過key來匹配原始樹和後來的樹。
// 舊 <ul> <li key="2015">Duke</li> <li key="2016">Villanova</li> </ul> // 新 <ul> <li key="2014">Connecticut</li> <li key="2015">Duke</li> <li key="2016">Villanova</li> </ul> 執行過程: 如今 React 知道帶有key '2014' 的元素是新的,對於 '2015' 和 '2016' 僅僅移動位置便可
<li key={item.id}>{item.name}</li>
npm i -S react react-dom
react
:react 是React庫的入口點react-dom
:提供了針對DOM的方法,好比:把建立的虛擬DOM,渲染到頁面上// 1. 導入 react import React from 'react' import ReactDOM from 'react-dom' // 2. 建立 虛擬DOM // 參數1:元素名稱 參數2:元素屬性對象(null表示無) 參數3:當前元素的子元素string||createElement() 的返回值 const divVD = React.createElement('div', { title: 'hello react' }, 'Hello React!!!') // 3. 渲染 // 參數1:虛擬dom對象 參數2:dom對象表示渲染到哪一個元素內 參數3:回調函數 ReactDOM.render(divVD, document.getElementById('app'))
createElement()
方式,代碼編寫不友好,太複雜var dv = React.createElement( "div", { className: "shopping-list" }, React.createElement( "h1", null, "Shopping List for " ), React.createElement( "ul", null, React.createElement( "li", null, "Instagram" ), React.createElement( "li", null, "WhatsApp" ) ) ) // 渲染 ReactDOM.render(dv, document.getElementById('app'))
npm i -D babel-preset-react
(依賴與:babel-core/babel-loader)注意:JSX的語法須要經過 babel-preset-react 編譯後,才能被解析執行
/* 1 在 .babelrc 開啓babel對 JSX 的轉換 */ { "presets": [ "env", "react" ] } /* 2 webpack.config.js */ module: [ rules: [ { test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ }, ] ] /* 3 在 js 文件中 使用 JSX */ const dv = ( <div title="標題" className="cls container">Hello JSX!</div> ) /* 4 渲染 JSX 到頁面中 */ ReactDOM.render(dv, document.getElementById('app'))
注意 1: 若是在 JSX 中給元素添加類, 須要使用 className
代替 classjava
htmlFor
代替{/* 中間是註釋的內容 */}
React 組件可讓你把UI分割爲獨立、可複用的片斷,並將每一片斷視爲相互獨立的部分。
props
),而且返回一個React對象,用來描述展現在頁面中的內容函數式組件 和 class 組件的使用場景說明: 1 若是一個組件僅僅是爲了展現數據,那麼此時就可使用 函數組件 2 若是一個組件中有必定業務邏輯,須要操做數據,那麼就須要使用 class 建立組件,由於,此時須要使用 state
null
()
包裹,避免換行問題function Welcome(props) { return ( // 此處註釋的寫法 <div className="shopping-list"> {/* 此處 註釋的寫法 必需要{}包裹 */} <h1>Shopping List for {props.name}</h1> <ul> <li>Instagram</li> <li>WhatsApp</li> </ul> </div> ) } ReactDOM.render( <Welcome name="jack" />, 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 ( <div className="shopping-list"> <h1>Shopping List for {this.props.name}</h1> <ul> <li>Instagram</li> <li>WhatsApp</li> </ul> </div> ) } }
只讀的對象
叫作 props
,沒法給props添加屬性props
props
對象中的屬性function Welcome(props){ // props ---> { username: 'zs', age: 20 } return ( <div> <div>Welcome React</div> <h3>姓名:{props.username}----年齡是:{props.age}</h3> </div> ) } // 給 Hello組件 傳遞 props:username 和 age(若是你想要傳遞numb類型是數據 就須要向下面這樣) ReactDOM.reander(<Hello username="zs" age={20}></Hello>, ......)
// 建立Hello2.js組件文件 // 1. 引入React模塊 // 因爲 JSX 編譯後會調用 React.createElement 方法,因此在你的 JSX 代碼中必須首先拿到React。 import React from 'react' // 2. 使用function構造函數建立組件 function Hello2(props){ return ( <div> <div>這是Hello2組件</div> <h1>這是大大的H1標籤,我大,我驕傲!!!</h1> <h6>這是小小的h6標籤,我小,我傲嬌!!!</h6> </div> ) } // 3. 導出組件 export default Hello2 // app.js中 使用組件: import Hello2 from './components/Hello2'
props
props
是隻讀的,沒法給props
添加或修改屬性props.children
:獲取組件的內容,好比:node
<Hello>組件內容</Hello>
中的 組件內容
// props 是一個包含數據的對象參數,不要試圖修改 props 參數 // 返回值:react元素 function Welcome(props) { // 返回的 react元素中必須只有一個根元素 return <div>hello, {props.name}</div> } class Welcome extends React.Component { constructor(props) { super(props) } render() { return <h1>Hello, {this.props.name}</h1> } }
狀態即數據
組件內部
使用的數據class
建立的組件才具備狀態注意:不要在 state
中添加 render()
方法中不須要的數據,會影響渲染性能!react
注意:不要在 render()
方法中調用 setState() 方法來修改state
的值android
this.state.name = 'rose'
方式設置state(不推薦!!!!)// 例: class Hello extends React.Component { constructor() { // es6繼承必須用super調用父類的constructor super() this.state = { gender: 'male' } } render() { return ( <div>性別:{ this.state.gender }</div> ) } }
// 一、JSX const element = ( <h1 className="greeting"> Hello, world! </h1> ) // 二、JSX -> createElement const element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!' ) // React elements: 使用對象的形式描述頁面結構 // Note: 這是簡化後的對象結構 const element = { type: 'h1', props: { className: 'greeting', }, children: ['Hello, world'] }
<CommentList></CommentList>
和 <Comment></Comment>
[
{ user: '張三', content: '哈哈,沙發' }, { user: '張三2', content: '哈哈,板凳' }, { user: '張三3', content: '哈哈,涼蓆' }, { user: '張三4', content: '哈哈,磚頭' }, { user: '張三5', content: '哈哈,樓下山炮' } ] // 屬性擴展 <Comment {...item} key={i}></Comment>
// 1. 直接寫行內樣式: <li style={{border:'1px solid red', fontSize:'12px'}}></li> // 2. 抽離爲對象形式 var styleH3 = {color:'blue'} var styleObj = { liStyle:{border:'1px solid red', fontSize:'12px'}, h3Style:{color:'green'} } <li style={styleObj.liStyle}> <h3 style={styleObj.h3Style}>評論內容:{props.content}</h3> </li> // 3. 使用樣式表定義樣式: import '../css/comment.css' <p className="pUser">評論人:{props.user}</p>
組件生命週期函數的定義:從組件被建立,到組件掛載到頁面上運行,再到頁面關閉組件被卸載,這三個階段老是伴隨着組件各類各樣的事件,那麼這些事件,統稱爲組件的生命週期函數!webpack
constructor()
componentWillMount()
render()
componentDidMount()
componentWillReceiveProps()
shouldComponentUpdate()
componentWillUpdate()
render()
componentDidUpdate()
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()
方法,不然會遞歸渲染ios
render()
,render()
又從新改變狀態render() {
console.warn(document.getElementById('btn')) // null return ( <div> <button id="btn" onClick={this.handleAdd}>打豆豆一次</button> { this.state.count === 4 ? null : <CounterChild initCount={this.state.count}></CounterChild> } </div> ) }
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) }
Mounting
階段的render
是同一個函數componentDidUpdate(prevProps, prevState) {
console.warn('componentDidUpdate', prevProps, prevState) }
做用:在卸載組件的時候,執行清理工做,好比
componentDidMount
建立的DOM對象React.createClass({})
方式,建立有狀態組件,該方式已經被廢棄!!!require('create-react-class')
,能夠在不適用ES6的狀況下,建立有狀態組件createReactClass()
方式建立組件中的兩個函數var createReactClass = require('create-react-class'); var Greeting = createReactClass({ // 初始化 props getDefaultProps: function() { console.log('getDefaultProps'); return { title: 'Basic counter!!!' } }, // 初始化 state getInitialState: function() { console.log('getInitialState'); return { count: 0 } }, render: function() { console.log('render'); return ( <div> <h1>{this.props.title}</h1> <div>{this.state.count}</div> <input type='button' value='+' onClick={this.handleIncrement} /> </div> ); }, handleIncrement: function() { var newCount = this.state.count + 1; this.setState({count: newCount}); }, propTypes: { title: React.PropTypes.string } }); ReactDOM.render( React.createElement(Greeting), document.getElementById('app') );
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
用來綁定單擊事件<input type="button" value="觸發單擊事件" onClick={this.handleCountAdd} onMouseEnter={this.handleMouseEnter} />
ref
屬性,而後,獲取元素綁定事件// JSX // 將當前DOM的引用賦值給 this.txtInput 屬性 <input ref={ input => this.txtInput = input } type="button" value="我是豆豆" /> componentDidMount() { // 經過 this.txtInput 屬性獲取元素綁定事件 this.txtInput.addEventListener(() => { this.setState({ count:this.state.count + 1 }) }) }
bind
綁定箭頭函數
綁定bind
可以調用函數,改變函數內部this的指向,並返回一個新函數bind
第一個參數爲返回函數中this的指向,後面的參數爲傳給返回函數的參數// 自定義方法: handleBtnClick(arg1, arg2) { this.setState({ msg: '點擊事件修改state的值' + arg1 + arg2 }) } render() { return ( <div> <button onClick={ // 無參數 // this.handleBtnClick.bind(this) // 有參數 this.handleBtnClick.bind(this, 'abc', [1, 2]) }>事件中this的處理</button> <h1>{this.state.msg}</h1> </div> ) }
bind
constructor() { super() this.handleBtnClick = this.handleBtnClick.bind(this) } // render() 方法中: <button onClick={ this.handleBtnClick }>事件中this的處理</button>
箭頭函數
中的this由所處的環境決定,自身不綁定this<input type="button" value="在構造函數中綁定this並傳參" onClick={ () => { this.handleBtnClick('參數1', '參數2') } } /> handleBtnClick(arg1, arg2) { this.setState({ msg: '在構造函數中綁定this並傳參' + arg1 + arg2 }); }
在HTML當中,像input
,textarea
和select
這類表單元素會維持自身狀態,並根據用戶輸入進行更新。
在React中,可變的狀態一般保存在組件的state
中,而且只能用setState()
方法進行更新.
React根據初始狀態渲染表單組件,接受用戶後續輸入,改變表單組件內部的狀態。
所以,將那些值由React控制的表單元素稱爲:受控組件。
受控組件的特色:
// 模擬實現文本框數據的雙向綁定 <input type="text" value={this.state.msg} onChange={this.handleTextChange}/> // 當文本框內容改變的時候,觸發這個事件,從新給state賦值 handleTextChange = event => { console.log(event.target.value) this.setState({ msg: event.target.value }) }
[
{name: '小明', content: '沙發!!!'}, {name: '小紅', content: '小明,竟然是你'}, {name: '小剛', content: '小明,放學你別走!!!'}, ]
npm i -S prop-types
propTypes
(對象),來約束props
// 引入模塊 import PropTypes from 'prop-types' // ...如下代碼是類的靜態屬性: // propTypes 靜態屬性的名稱是固定的!!! static propTypes = { initCount: PropTypes.number, // 規定屬性的類型 initAge: PropTypes.number.isRequired // 規定屬性的類型,且規定爲必傳字段 }
react中的單向數據流動: 1 數據應該是從上往下流動的,也就是由父組件將數據傳遞給子組件 2 數據應該是由父組件提供,子組件要使用數據的時候,直接從子組件中獲取 在咱們的評論列表案例中:數據是由CommentList組件(父組件)提供的 子組件 CommentItem 負責渲染評論列表,數據是由 父組件提供的 子組件 CommentForm 負責獲取用戶輸入的評論內容,最終也是把用戶名和評論內容傳遞給了父組件,由父組件負責處理這些數據( 把數據交給 CommentItem 由這個組件負責渲染 )
props
注意:若是不熟悉React中的數據流,不推薦使用這個屬性
props
PropTypes
類型限制來使用class Grandfather extends React.Component { // 類型限制(必須),靜態屬性名稱固定 static childContextTypes = { color: PropTypes.string.isRequired } // 傳遞給孫子組件的數據 getChildContext() { return { color: 'red' } } render() { return ( <Father></Father> ) } } class Child extends React.Component { // 類型限制,靜態屬性名字固定 static contextTypes = { color: PropTypes.string } render() { return ( // 從上下文對象中獲取爺爺組件傳遞過來的數據 <h1 style={{ color: this.context.color }}>爺爺告訴文字是紅色的</h1> ) } } class Father extends React.Component { render() { return ( <Child></Child> ) } }
npm i -S react-router-dom
Router
組件自己只是一個容器,真正的路由要經過Route組件
定義2 使用 <Router></Router>
做爲根容器,包裹整個應用(JSX)
<Link to="/movie"></Link>
做爲連接地址,並指定to
屬性<Route path="/" compoent={Movie}></Route>
展現路由內容// 1 導入組件 import { HashRouter as Router, Link, Route } from 'react-router-dom' // 2 使用 <Router> <Router> // 3 設置 Link <Menu.Item key="1"><Link to="/">首頁</Link></Menu.Item> <Menu.Item key="2"><Link to="/movie">電影</Link></Menu.Item> <Menu.Item key="3"><Link to="/about">關於</Link></Menu.Item> // 4 設置 Route // exact 表示:絕對匹配(徹底匹配,只匹配:/) <Route exact path="/" component={HomeContainer}></Route> <Route path="/movie" component={MovieContainer}></Route> <Route path="/about" component={AboutContainer}></Route> </Router>
<Router></Router>
:做爲整個組件的根元素,是路由容器,只能有一個惟一的子元素<Link></Link>
:相似於vue中的<router-link></router-link>
標籤,to
屬性指定路由地址<Route></Route>
:相似於vue中的<router-view></router-view>
,指定路由內容(組件)展現位置Route
中的path屬性來配置路由參數this.props.match.params
獲取// 配置路由參數 <Route path="/movie/:movieType"></Route> // 獲取路由參數 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 }
原文:http://www.javashuo.com/article/p-ehnkykkb-k.html/* 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: '要添加的任務名稱' } )