-
受控組件 V.S. 非受控組件
<FInput value={x} onChange={fn}/> 受控組件:渲染表單的React組件 還控制著 用戶輸入表單時變化的操作,每次狀態變化都調用onChange,這種組件叫受控組件。表單數據是由 React 組件來管理的。 <FInput defaultValue={x} ref={input}/> 非受控組件:React賦予組件一個初始值,但是不去控制后續的更新。表單數據將交由 DOM 節點來處理。
-
React
有哪些生命周期函數?分別有什么用?(Ajax
請求放在哪個階段?)
初始化階段:
componentWillMount()
組件即將被渲染到頁面之前觸發,此時可以進行開啟定時器、向服務器發送請求等操作
render()
組件渲染
componentDidMount()
組件已經被渲染到頁面中后觸發:此時頁面中有了真正的DOM
的元素,可以進行DOM
相關的操作
運行中階段:
componentWillReceiveProps()
組件接收到屬性時觸發
shouldComponentUpdate()
當組件接收到新屬性,或者組件的狀態發生改變時觸發。組件首次渲染時并不會觸發
componentWillUpdate()
組件即將被更新時觸發
render()
componentDidUpdate()
組件被更新完成后觸發。頁面中產生了新的DOM的元素,可以進行DOM操作
銷毀階段
componentWillUnmount()
組件被銷毀時觸發。- 在
componentDidMount()
中請求數據。
- 在
-
React
如何實現組件間通信?- 父子靠
props
傳函數 - 爺孫可以穿兩次
props
- 任意組件用
Redux
(也可以自己寫一個eventBus
)
- 父子靠
-
shouldComponentUpdate
有什么用?- 用于在沒有必要更新 UI 的時候返回
false
,以提高渲染性能。 - 但是不要濫用,這是
React
提供的一個緊急出口,必要的時候再使用,因為它的維護成本比較高,比如你新加了一個props
,但是又忘記在shouldComponentUpdate
寫,就會引起bug。 - 如何優化?
- 用于在沒有必要更新 UI 的時候返回
- 虛擬 DOM 是什么?
- 要點:虛擬 DOM 就是用來模擬 DOM 的一個對象,這個對象擁有一些重要屬性,并且更新 UI 主要就是通過對比(DIFF)舊的虛擬 DOM 樹 和新的虛擬 DOM 樹的區別完成的。
- 參考:http://foio.github.io/virtual-dom/
- 什么是高階組件?
- 文檔原話——高階組件就是一個函數,且該函數接受一個組件作為參數,并返回一個新的組件。
- React-Redux 里 connect 就是一個高階組件,比如
connect(mapState)(MyComponent)
接受組件 MyComponent,返回一個具有狀態的新 MyComponent 組件。
- React diff 的原理是什么?
首先,React diff遵循三個策略:1. Web UI 中,DOM節點跨層級的移動操作特別少,可以忽略不計。2.擁有相同類的兩個組件將生成相似的樹形結構,擁有不同類的兩個組件將生成不同的樹形結構。3. 對于同一層級的一組子節點,可以通過唯一id來區分它們?;谝陨先齻€策略,React分別對tree diff、component diff、element diff進行了算法優化。
-
tree diff
React對樹的算法進行了優化,對樹進行分層比較,兩棵樹只比較同一層級的節點。DOM節點跨層級的操作少到忽略不計,針對這一點,React通過updateDepth對Virtual DOM樹進行層級控制,只會對相同顏色方塊內的DOM節點進行比較,當發現節點不存在,則會刪除該節點以及所有子節點,不會再進行進一步比較,所以只要對DOM樹進行一次遍歷,就能完成整個DOM樹的比較。
tree diff.jpg -
component diff
如果是同一類型的組件,則按照原策略進行Virtual DOM Tree的比較。如果不是呢,則講組件判斷為dirty component,從而替換整個組件下的所有子節點。
對于同一類型的組件,React允許用戶使用shouldComponentUpdate()
來判斷該組件是否需要diff。
如下圖,當component D 改變為 component G時,即使兩個組件結構相似,一旦React判斷兩者為不同類型的組件,則不會進行比較,而是直接刪除component D,重新創建component G以及所有子節點。
component diff.jpg element diff
當節點處于同一層級時,React diff提供了三種節點操作
INSERT_MARKUP(插入):新的component類型不在老集合里,即是全新的節點,則需要對新節點執行插入操作。
MOVE_EXISTING(移動):新的component類型在老集合里,且element是可更新的類型,generateComponentChildren
已調用receiveComponent
,這種情況下prevChild=nextChild
,就需要執行移動操作,可以復用以前的DOM節點。
REMOVE_NODE(刪除):老component類型在新集合里面也有,但對應的element不同則不能直接進行復用和更新,需要執行刪除操作。老component類型在新集合里沒有,也要執行刪除操作。
開發者對同一層級的子節點,可以添加唯一索引進行區分,這樣在diff時,涉及到只是位置變化的,可以只移動元素,避免刪除創建等重復的操作。
- Redux 是什么?
-
Redux
是JavaScript
狀態管理工具,提供可預測化的狀態管理。 -
Action
:是把數據從應用傳到Store
的有效載荷。它是Store
數據的唯一來源。改變State
的唯一辦法,就是使用 Action。
-
import { ADD_TODO } from "../actionTypes";
export const addTodo = (payload:any) => {
return {
type: ADD_TODO,
payload
}
}
Reducers
: 指定了應用狀態的變化如何響應 actions
并發送到store
的,記住 actions
只是描述了有事情發生了這一事實,并沒有描述應用如何更新 state
。
Reducer
是一個函數,它接受 Action
和當前State
作為參數,返回一個新的 State
。
我們還可以將拆分后的Reducer
放到不同的文件中, 以保持其獨立性并用于專門處理不同的數據域。然后使用combineReducers
將多個Reducer
合并成一個,輸出成一個大的對象。
import { ADD_TODO } from "../actionTypes";
export default function (state = [],action:any) {
switch (action.type){
case ADD_TODO:
return [...state,action.payload]
default:
return state
}
}
import { ADD_TOMATO } from "../actionTypes";
export default function (state = [], action: any) {
switch(action.type) {
case ADD_TOMATO:
return [...state, action.payload]
default:
return state
}
}
//使用combineReducers
import { combineReducers } from "redux";
import todos from './todos'
import tomatoes from './tomatoes'
export default combineReducers({ todos, tomatoes });
Store
: 就是保存數據的地方,你可以把它看成一個容器。整個應用只能有一個 Store
。Redux
提供createStore
這個函數,接受reducers
,用來生成 Store
。
import { createStore } from "redux";
import rootReducer from "./reducers";
const store = createStore(rootReducer)
export default store
Store
有以下職責:
- 維持應用的
state
; - 提供
getState()
方法返回應用當前的 state 樹。它與 store 的最后一個 reducer 返回值相同。 - 提供
dispatch(action)
方法分發action
。這是觸發state
變化的惟一途徑。 - 通過
subscribe(listener)
注冊監聽器; - 通過
subscribe(listener)
返回的函數注銷監聽器。
connect
:該API連接React
組件與 Redux store
,連接操作不會改變原來的組件類。反而返回一個新的已與 Redux store 連接的組件類。connect可以接受參數,將參數注入到組件當中
const mapStateToProps = (state: { todos: any; }, ownProps: any) => ({//注入props
...ownProps
})
const mapDispatchToProps = {//注入屬性
editTodo,
updateTodo
}
export default connect(mapStateToProps, mapDispatchToProps)(todoItem)
<Provider store>
使組件層級中的 connect()
方法都能夠獲得 Redux store
。正常情況下,你的根組件應該嵌套在 <Provider>
中才能使用 connect()
方法。
ReactDOM.render(
<Provider store={store}>
<MyRootComponent />
</Provider>,
rootEl
)
- connect 的原理是什么?
react-redux
庫提供的一個 API,connect
的作用是讓你把組件和store
連接起來,產生一個新的組件(connect 是高階組件)。
1.首先connect
之所以會成功,是因為Provider
組件:
- 在原應用組件上包裹一層,使原來整個應用成為Provider
的子組件
- 接收Redux
的store
作為props
,通過context
對象傳遞給子孫組件上的connect
2.connect
做了什么:它真正連接Redux
和React
,它包在我們的容器組件的外一層,它接收上面Provider
提供的store
里面的state
和 dispatch
,傳給一個構造函數,返回一個對象,以屬性形式傳給我們的容器組件。
3.主邏輯源碼
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
return function wrapWithConnect(WrappedComponent) {
class Connect extends Component {
constructor(props, context) {
// 從祖先Component處獲得store
this.store = props.store || context.store
this.stateProps = computeStateProps(this.store, props)
this.dispatchProps = computeDispatchProps(this.store, props)
this.state = { storeState: null }
// 對stateProps、dispatchProps、parentProps進行合并
this.updateState()
}
shouldComponentUpdate(nextProps, nextState) {
// 進行判斷,當數據發生改變時,Component重新渲染
if (propsChanged || mapStateProducedChange || dispatchPropsChanged) {
this.updateState(nextProps)
return true
}
}
componentDidMount() {
// 改變Component的state
this.store.subscribe(() = {
this.setState({
storeState: this.store.getState()
})
})
}
render() {
// 生成包裹組件Connect
return (
<WrappedComponent {...this.nextState} />
)
}
}
Connect.contextTypes = {
store: storeShape
}
return Connect;
}
}
connect
是一個高階函數,首先傳入mapStateToProps
、mapDispatchToProps
等參數,然后返回一個生產Component
的函數(wrapWithConnect
),然后再將真正的Component
作為參數傳入wrapWithConnect
,這樣就生產出一個經過包裹的Connect
組件,該組件具有如下特點:
- 通過
props.store
獲取祖先Component
的store
-
props
包括stateProps
、dispatchProps
、parentProps
,合并在一起得到nextState
,作為props
傳給真正的Component
-
componentDidMount
時,添加事件this.store.subscribe(this.handleChange)
,實現頁面交互 -
shouldComponentUpdate
時判斷是否有避免進行渲染,提升頁面性能,并得到nextState
-
componentWillUnmount
時移除注冊的事件this.handleChange