import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; class App extends React.Component { state = { title: '環球大前端' } render() { const { title } = this.state; const { name } = this.props return ( <div> <h2>{title}</h2> <p> Hello {name}! </p> </div> ) } } App.propTypes = { name: PropTypes.string } App.defaultProps = { name: '帥氣小夥子' } ReactDOM.render(<App name="24小清新" />, document.getElementById('app'));
var App = React.createClass({ getDefaultProps: function() { return { name: '帥氣小夥子' } }, getInitialState: function() { return { title: '環球大前端' } }, render: function() { return ( <div> <h2>{this.state.title}</h2> <p> Hello {this.props.name}! </p> </div> ) } }) React.render(<App name="24小清新" />, document.getElementById('app'));
核心思想:封裝組件,各個組件維護本身的狀態(state, prop)和UI,當狀態變動,自動從新渲染組件,數據流向是單向的。javascript
須要明白的幾個基礎概念:html
一、什麼是JSX?前端
二、如何修改組件state,從而修改組件UI?java
三、事件處理react
對於上述那些既不是字符串也不是 HTML的的標籤語法,被稱爲JSX,是一種 JavaScript 的語法擴展,用來描述用戶界面。es6
經常使用的是在JSX中使用表達式,例如 2 + 2, user.firstName, 以及 formatName(user),條件判斷(三目運算符、&&), 數組Map函數遍歷獲取React元素 都是可使用的。如:ajax
formatName(user) { return `${user.firstName}-${user.name}`; } const user = { firstName: 'wu', name: 'shaobin' } const show = true; //我能夠是this.state屬性哦!!! const arr = ['xiaobin', 'kaizi', 'liujun']; const element = ( <div> <h1>Hello, {formatName(user)}!</h1> <h1>Hello, {user.name}!</h1> <h1>Hello, { 1 + 1 }!</h1> <h1>Hello, { show ? 'I am show' : null }</h1> <h1>Hello, { arr.length > 0 && <span>數組長度大於0</span> }</h1> { arr.map((item, index) => { return <span key={item}>item</span> }) } //記住數組Map函數遍歷獲取React元素的時候,必需要記得必須➕keys屬性 // 爲啥呀? //Keys能夠在DOM中的某些元素被增長或刪除的時候幫助React識別哪些元素髮生了變化。所以你應當給數組中的每個元素賦予一個肯定的標識。沒有惟一值的時候可使用index,可是官方不建議,會致使渲染變慢。 </div> ); ReactDOM.render( element, document.getElementById('root') );
切記每一個組件的render函數返回的JSX結構都須要根元素去包裹着,固然也有例外,如React.Fragmentredux
對於JSX,react最終經babel的轉換會調用React.createElement相應api轉換成react能識別的對象,如上述例子轉換後獲得:api
React.createElement( 'div', null, React.createElement( 'h2', //能夠是一個html標籤名稱字符串,也能夠是也能夠是一個 React component 類型 null, title ), React.createElement( 'p', null, ' Hello ', name, '! ' ) );
既然咱們能夠爲組件初始化狀態,也必需要可以去改變它,以達到改變視圖。
固然this.state.xxx = xxx
不會觸發渲染組件的動做,而是使用this.setState({ xxx: xxx })
方法來修改狀態,同時多個setState() 調用合併成一個調用能提升性能。
對於事件處理,須要注意的一點就是this的綁定,其餘跟普通Dom綁定監聽事件同樣,this的綁定有如下幾種方式:
同時須要注意的是,React合成事件(onClick={})和原生事件(document.addEventListener)的阻止冒泡存在差別,須要明白的是,全部合成事件都是綁定在document上的(代理),因此執行合成事件中的event.stopPropagation(),實際原生事件仍是會冒泡到document上,同時須要注意的是,setState 只在合成事件和生命週期函數中是 "異步" 的,在原生事件和 setTimeout 中都是同步的。
合成事件與原生事件列子
參考文章:
從 Dropdown 的 React 實現中學習到的
React合成事件和DOM原生事件混用須知
一、有哪些生命週期,生命週期的執行順序?
二、Ref的引用
三、高階組件的使用
constructor() //React組件的構造函數,用於super(props),初始化state,bind綁定事件等
static getDerivedStateFromProps()
UNSAFE_componentWillMount() // 組件掛載前(組件渲染到頁面前)
render() // 渲染函數,不作實際的渲染動做,它只是返回一個JSX描述的結構,生成虛擬Dom樹,執行patch,最終由React來操做渲染過程
componentDidMount() //組件掛載後(組件渲染到頁面上),能夠在這個鉤子添加異步請求以及定時器,或是socket鏈接
UNSAFE_componentWillReceiveProps() // 組件接收到屬性時觸發
static getDerivedStateFromProps()
shouldComponentUpdate(prevProps, prevState) // 當組件接收到新屬性,或者組件的狀態發生改變時觸發。組件首次渲染時並不會觸發,此鉤子可作性能優化
UNSAFE_componentWillUpdate() //組件即將被更新時觸發
render() // 渲染函數,不作實際的渲染動做,它只是返回一個JSX描述的結構,生成虛擬新Dom樹,與舊樹進行diff, 執行patch
getSnapshotBeforeUpdate()
componentDidUpdate(prevProps, prevState, snapshot) // 組件被更新完成後觸發,生命週期中因爲state的變化觸發請求,在componentDidUpdate中進行
componentWillUnmount() // 卸載組件,註銷監聽事件或是定時器,socket關閉
詳細看生命週期例子
getSnapshotBeforeUpdate()與static getDerivedStateFromProps()兩個新增生命週期鉤子是被用來代替 UNSAFE_componentWillMount() ,UNSAFE_componentWillUpdate(), UNSAFE_componentWillReceiveProps()三個生命週期的,可是這個三個生命週期還是可使用。爲何勒?React爲了1.7版本實現Async Rendering。
Refs 提供了一種方式,用於訪問在 render 方法中建立的 DOM 節點或 React 元素,官方建議少用。獲取Ref有三種場景:
獲取Ref的經常使用方式(經過this.myRef.current來獲取Dom節點或實例):
class MyComponent extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); // 調用React.createRef API } render() { return <input ref={this.myRef} />; } }
class MyComponent extends React.Component { constructor(props) { super(props); } render() { return <input ref={(ref) => { this.myRef = ref; }} />; } }
Ref獲取Dom元素例子, Ref獲取React元素(子組件實例)例子。
補充Tip:
對於第三種狀況,獲取子組件的Dom節點,官方有提供Forwarding Refs(轉發Ref)的方法,來獲取子組件的Dom節點的Ref,此方法返回的是一個React元素,對應方法爲React.forwardRef((props, ref) => { ... })
高階組件(HOC)是react中對組件邏輯進行重用的高級技術。但高階組件自己並非React API。它只是一種模式,其實就是一個函數,且該函數接受一個組件做爲參數,並返回一個新的組件。高階組件在React第三方庫中很常見,好比Redux的connect方法和react-router的withRouter()方法。
注意事項:
一、不要再render函數中使用高階組件,否則會致使每次從新渲染,都會從新建立高階組件實例,銷燬掉舊的高階組件,致使全部狀態和子組件都被卸載。
二、必須將靜態方法作拷貝,當使用高階組件包裝組件,原始組件被容器組件包裹,獲得新組件會丟失原始組件的全部靜態方法,假如原始組件有靜態方法,可使用hoist-non-react-statics進行靜態方法拷貝。例子
三、Refs屬性不能傳遞,高階組件能夠傳遞全部的props屬性給包裹的組件,可是不能傳遞refs引用,可是有時候咱們確實須要把ref的引用傳給包裹組件,能夠傳一個非ref命名的props屬性給到高階組件上,由高階組件綁定到包裹組件的ref上,也可使用轉發Ref。例子
Redux主要分紅三部分,分別爲Store,Action,Reducer,下面是對三部分的通俗的講解:
Store:Redux應用只有一個單一的Store,就是單一數據源,將整個應用共享的狀態state儲存在一棵對象樹上面,注意的是,對象樹上面的state是隻讀的,只能經過純函數來執行修改,建立store,是經過Redux的 createStore(reducer)方法來建立的,store裏面會有getState()、dispatch()、subscribe(listener)的方法。
Action:一個普通的Javascript對象,描述了應用state發生了什麼變化,經過dispatch方法來通知store調用reducer方法。
Reducer:描述應用如何更新state,自己是一個函數,接受Action參數,返回新的state。
須要明白的一點Redux跟React一點關係都沒有,可是React搭配Redux來實現狀態管理時最好的實現方案。那麼如何搭配呢?原本咱們能夠subscribe(listener)在react的組件註冊redux的監聽器,可是這種方式繁瑣,並且會致使屢次渲染。因此搭配着react-redux來使用。基本使用以下:
import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' import { createStore } from 'redux' import App from './components/App' const todoApp = (state = {}, action) { if (actions.type === 'SHOW') { return Object.assign({}, state, { show: action.show }); } return state; } let store = createStore(todoApp) render( // 使用指定的 React Redux 組件 <Provider> 來讓全部容器組件均可以訪問 store,而沒必要顯示地傳遞它。只須要在渲染根組件時使用便可。 <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
import React from 'react' import { connect } from 'react-redux'; import Child from './components/Child' class App extends Component { render() { const { show } = this.props; return ( <div> <Child show={show }/> </div> ); } } const stateToProps = (state) => ({ show: state.show }); export default connect(stateToProps)(App);
react-redux內容實現原理,使用的Context API,簡單來講,父組件聲明須要跨層級組件傳遞的屬性(childContextType)以及監聽屬性變化的getChildContext()函數,子組件聲明能夠接收上層組件傳遞的屬性(contextType)。
若是存在多個reducer的話,可使用redux中的combineReducers進行合併,代碼以下:
import { createStore, combineReducers } from 'redux'; const todoApp = combineReducers({ reducer1, reducer2 }) const store = createStore(todoApp);
代碼等價於:
import { createStore} from 'redux'; const todoApp = (state = {}, action) => { return { reducer1: state.reducer1, reducer2: state.reducer2 } } const store = createStore(todoApp);
combineReducers最終只是生成一個函數,這個函數來調用你的一系列 reducer,每一個 reducer 根據它們的 key 來篩選出 state 中的一部分數據並處理,而後這個生成的函數再將全部 reducer 的結果合併成一個大的對象。詳細邏輯能夠看redux源碼。
對於路由規則,咱們在項目裏面搭配的是react-router v4這個庫來完成的,因爲咱們這以前也沒接觸過react-router,因此版本v3與v4之間模式和策略的差別不一樣也沒有帶來思惟模式轉換的困難,下面先帖碼簡單看看v3與v4版本之間的差別性(摘自掘金):
import { Router, Route, IndexRoute } from 'react-router' const PrimaryLayout = props => ( <div className="primary-layout"> <header> Our React Router 3 App </header> <main> {props.children} </main> </div> ) const HomePage =() => <div>Home Page</div> const UsersPage = () => <div>Users Page</div> const App = () => ( <Router history={browserHistory}> <Route path="/" component={PrimaryLayout}> <IndexRoute component={HomePage} /> <Route path="/users" component={UsersPage} /> </Route> </Router> ) render(<App />, document.getElementById('root'))
import { BrowserRouter, Route } from 'react-router-dom' const PrimaryLayout = () => ( <div className="primary-layout"> <header> Our React Router 4 App </header> <main> <Route path="/" exact component={HomePage} /> <Route path="/users" component={UsersPage} /> </main> </div> ) const HomePage =() => <div>Home Page</div> const UsersPage = () => <div>Users Page</div> const App = () => ( <BrowserRouter> <PrimaryLayout /> </BrowserRouter> ) render(<App />, document.getElementById('root'))
差別性:
一、v3是集中性路由,全部路由都是集中在一個地方, 而v4則相反。
二、v3佈局和頁面嵌套是經過 組件的嵌套而來的,而v4不會互相嵌套
三、v3佈局和頁面組件是徹底純粹的,它們是路由的一部分,而v4路由規則位於佈局和 UI 自己之間
四、使用v4須要在咱們的組件根部用BrowserRouter組件(用於瀏覽器)去包裝,實現原理與react-redux的Provider組件同樣(Context API),以便組件能夠去拿到路由信息。
這裏主要介紹包容性路由、排他性路由、嵌套路由,以及withRouter的一些基本用法。