一、React初探 es6寫法 "code" es5寫法(遺憾的是現在最新版本的react,已經不再能使用createClass去創建react組件了 "code" ) 核心思想:封裝組件,各個組件維護自己的狀態(state, prop)和UI,當狀態變更,自動重新渲染組件,數據流向是單向的。 需要 ...
一、React初探
es6寫法 code
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'));
es5寫法(遺憾的是現在最新版本的react,已經不再能使用createClass去創建react組件了 code)
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,當狀態變更,自動重新渲染組件,數據流向是單向的。
需要明白的幾個基礎概念:
1、什麼是JSX?
2、如何修改組件state,從而修改組件UI?
3、事件處理
對於上述那些既不是字元串也不是 HTML的的標簽語法,被稱為JSX,是一種 JavaScript 的語法擴展,用來描述用戶界面。
常用的是在JSX中使用表達式,例如 2 + 2, user.firstName, 以及 formatName(user),條件判斷(三目運算符、&&), 數組Map函數遍歷獲取React元素 都是可以使用的。如:
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.Fragment
對於JSX,react最終經babel的轉換會調用React.createElement相應api轉換成react能識別的對象,如上述例子轉換後得到:
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的綁定有以下幾種方式:
- 在構造函數中使用bind綁定this
- 在調用的時候使用bind綁定this
- 在調用的時候使用箭頭函數綁定this
- 使用屬性初始化器語法綁定this
二、React進階
1、有哪些生命周期,生命周期的執行順序?
2、Ref的引用
3、高階組件的使用
Mounting/掛載
constructor() //React組件的構造函數,用於super(props),初始化state,bind綁定事件等
static getDerivedStateFromProps()
UNSAFE_componentWillMount() // 組件掛載前(組件渲染到頁面前)
render() // 渲染函數,不做實際的渲染動作,它只是返回一個JSX描述的結構,生成虛擬Dom樹,執行patch,最終由React來操作渲染過程
componentDidMount() //組件掛載後(組件渲染到頁面上),可以在這個鉤子添加非同步請求以及定時器,或是socket連接
Updating/更新
UNSAFE_componentWillReceiveProps() // 組件接收到屬性時觸發
static getDerivedStateFromProps()
shouldComponentUpdate(prevProps, prevState) // 當組件接收到新屬性,或者組件的狀態發生改變時觸發。組件首次渲染時並不會觸發,此鉤子可做性能優化
UNSAFE_componentWillUpdate() //組件即將被更新時觸發
render() // 渲染函數,不做實際的渲染動作,它只是返回一個JSX描述的結構,生成虛擬新Dom樹,與舊樹進行diff, 執行patch
getSnapshotBeforeUpdate()
componentDidUpdate(prevProps, prevState, snapshot) // 組件被更新完成後觸發,生命周期中由於state的變化觸發請求,在componentDidUpdate中進行
Unmounting/卸載
componentWillUnmount() // 卸載組件,註銷監聽事件或是定時器,socket關閉
詳細看生命周期例子
補充Tip:
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()方法。
註意事項:
1、不要再render函數中使用高階組件,不然會導致每次重新渲染,都會重新創建高階組件實例,銷毀掉舊的高階組件,導致所有狀態和子組件都被卸載。
2、必須將靜態方法做拷貝,當使用高階組件包裝組件,原始組件被容器組件包裹,得到新組件會丟失原始組件的所有靜態方法,假如原始組件有靜態方法,可以使用hoist-non-react-statics進行靜態方法拷貝。例子
3、Refs屬性不能傳遞,高階組件可以傳遞所有的props屬性給包裹的組件,但是不能傳遞refs引用,但是有時候我們確實需要把ref的引用傳給包裹組件,可以傳一個非ref命名的props屬性給到高階組件上,由高階組件綁定到包裹組件的ref上,也可以使用轉發Ref。例子
三、React第三方庫
1、Redux與react-redux
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源碼。
2、react-router
對於路由規則,我們在項目裡面搭配的是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'))
差異性:
1、v3是集中性路由,所有路由都是集中在一個地方, 而v4則相反。
2、v3佈局和頁面嵌套是通過 組件的嵌套而來的,而v4不會互相嵌套
3、v3佈局和頁面組件是完全純粹的,它們是路由的一部分,而v4路由規則位於佈局和 UI 本身之間
4、使用v4需要在我們的組件根部用BrowserRouter組件(用於瀏覽器)去包裝,實現原理與react-redux的Provider組件一樣(Context API),以便組件可以去拿到路由信息。
這裡主要介紹包容性路由、排他性路由、嵌套路由,以及withRouter的一些基本用法。