React16
經常使用api
解析以及原理剖析Vue
與 React
兩個框架的粗略區別對比react 16
版本常見 api
react
生命週期react
事件機制react.Component
如何實現組件化以及高階組件的應用setState
異步隊列數據管理react Fiber
架構分析react hooks
dom
的 diff
算法snabbdom
源碼,是怎樣實現精簡的 Virtual DOM
的redux
單向數據流架構如何設計Vue
與 React
兩個框架的粗略區別對比Vue 的優點包括:css
React 的優點包括:html
React 與 Vue 有不少類似之處,React 和 Vue 都是很是優秀的框架,它們之間的類似之處多過不一樣之處,而且它們大部分最棒的功能是相通的:如他們都是 JavaScript 的 UI 框架,專一於創造前端的富應用。不一樣於早期的 JavaScript 框架「功能齊全」,Reat 與 Vue 只有框架的骨架,其餘的功能如路由、狀態管理等是框架分離的組件。前端
從二者的 github 表現來看(數據取於 2019-09-16)vue
能夠看出 vue 的 star 數量已是前端框架中最火爆的。從維護上來看,react 是 facebook 在維護,而 vue 現階段雖然也有了團隊,但主要仍是尤雨溪在維護貢獻代碼,而且阿里巴巴開源的混合式框架 weex 也是基於 vue 的,因此咱們相信 vue 將來將會獲得更多的人和團隊維護。node
根據不徹底統計,包括餓了麼、簡書、高德、稀土掘金、蘇寧易購、美團、天貓、荔枝 FM、房多多、Laravel、htmlBurger 等國內外知名大公司都在使用 vue 進行新項目的開發和舊項目的前端重構工做。react
使用 React 的公司 facebook、Twitter、INS、Airbnb、Yahoo、ThoughtWorks、螞蟻金服、阿里巴巴、騰訊、百度、口碑、美團、滴滴出行、餓了麼、京東、網易等。webpack
vue | react | |
---|---|---|
pc 端 | iview、element 等 | Ant Design、Materal-UI 等 |
h5 端 | 有贊 vant、mintui 等 | Ant Design Mobile、weui |
混合開發 | weexui、bui-weex | teaset、react-native-elements |
微信小程序 | iview、Weapp、zanui | iView Weapp、Taro UI |
不管您選擇React.js仍是Vue.js,兩個框架都沒有至關大的差別,根據您的要求,這個決定是很是主觀的。若是您想將前端JavaScript框架集成到現有應用程序中,Vue.js是更好的選擇,若是您想使用JavaScript構建移動應用程序,React絕對是您的選擇。git
react16
版本常見 api
先來看一下 react 暴露出來的 APIgithub
const React = {
Children: {
map,
forEach,
count,
toArray,
only
},
createRef,
Component,
PureComponent,
createContext,
forwardRef,
Fragment: REACT_FRAGMENT_TYPE,
StrictMode: REACT_STRICT_MODE_TYPE,
unstable_AsyncMode: REACT_ASYNC_MODE_TYPE,
unstable_Profiler: REACT_PROFILER_TYPE,
createElement: __DEV__ ? createElementWithValidation : createElement,
cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
isValidElement: isValidElement,
version: ReactVersion,
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals
}
複製代碼
Children
這個對象提供了一堆幫你處理 props.children 的方法,由於 children 是一個相似數組可是不是數組的數據結構,若是你要對其進行處理能夠用 React.Children 外掛的方法。web
createRef
新的 ref 用法,React 即將拋棄<div ref="myDiv" />
這種 string ref 的用法,未來你只能使用兩種方式來使用 ref
class App extends React.Component {
constructor() {
this.ref = React.createRef()
}
render() {
return <div ref={this.ref} />
// or
return <div ref={node => (this.funRef = node)} />
}
}
複製代碼
createContext
createContext
是官方定稿的 context 方案,在這以前咱們一直在用的老的 context API 都是 React 不推薦的 API,如今新的 API 釋出,官方也已經肯定在 17 大版本會把老 API 去除(老 API 的性能不是通常的差)。
新 API 的使用方法:
const { Provider, Consumer } = React.createContext('defaultValue')
const ProviderComp = (props) => (
<Provider value={'realValue'}> {props.children} </Provider>
)
const ConsumerComp = () => (
<Consumer> {(value) => <p>{value}</p>} </Consumber>
)
複製代碼
目前 react 16.8 +的生命週期分爲三個階段,分別是掛載階段、更新階段、卸載階段
constructor(props)
: 實例化。static getDerivedStateFromProps
從 props
中獲取 state
。render
渲染。componentDidMount
: 完成掛載。static getDerivedStateFromProps
從 props 中獲取 state。shouldComponentUpdate
判斷是否須要重繪。render
渲染。getSnapshotBeforeUpdate
獲取快照。componentDidUpdate
渲染完成後回調。componentWillUnmount
即將卸載。static getDerivedStateFromError
從錯誤中獲取 state
。componentDidCatch
捕獲錯誤並進行處理。class ExampleComponent extends react.Component {
// 構造函數,最早被執行,咱們一般在構造函數裏初始化state對象或者給自定義方法綁定this
constructor() {}
//getDerivedStateFromProps(nextProps, prevState)用於替換 `componentWillReceiveProps` ,該函數會在初始化和 `update` 時被調用
// 這是個靜態方法,當咱們接收到新的屬性想去修改咱們state,可使用getDerivedStateFromProps
static getDerivedStateFromProps(nextProps, prevState) {
// 新的鉤子 getDerivedStateFromProps() 更加純粹, 它作的事情是將新傳進來的屬性和當前的狀態值進行對比, 若不一致則更新當前的狀態。
if (nextProps.riderId !== prevState.riderId) {
return {
riderId: nextProps.riderId
}
}
// 返回 null 則表示 state 不用做更新
return null
}
// shouldComponentUpdate(nextProps, nextState),有兩個參數nextProps和nextState,表示新的屬性和變化以後的state,返回一個布爾值,true表示會觸發從新渲染,false表示不會觸發從新渲染,默認返回true,咱們一般利用今生命週期來優化react程序性能
shouldComponentUpdate(nextProps, nextState) {
return nextProps.id !== this.props.id
}
// 組件掛載後調用
// 能夠在該函數中進行請求或者訂閱
componentDidMount() {}
// getSnapshotBeforeUpdate(prevProps, prevState):這個方法在render以後,componentDidUpdate以前調用,有兩個參數prevProps和prevState,表示以前的屬性和以前的state,這個函數有一個返回值,會做爲第三個參數傳給componentDidUpdate,若是你不想要返回值,能夠返回null,今生命週期必須與componentDidUpdate搭配使用
getSnapshotBeforeUpdate() {}
// 組件即將銷燬
// 能夠在此處移除訂閱,定時器等等
componentWillUnmount() {}
// 組件銷燬後調用
componentDidUnMount() {}
// componentDidUpdate(prevProps, prevState, snapshot):該方法在getSnapshotBeforeUpdate方法以後被調用,有三個參數prevProps,prevState,snapshot,表示以前的props,以前的state,和snapshot。第三個參數是getSnapshotBeforeUpdate返回的,若是觸發某些回調函數時須要用到 DOM 元素的狀態,則將對比或計算的過程遷移至 getSnapshotBeforeUpdate,而後在 componentDidUpdate 中統一觸發回調或更新狀態。
componentDidUpdate() {}
// 渲染組件函數
render() {}
// 如下函數不建議使用
UNSAFE_componentWillMount() {}
UNSAFE_componentWillUpdate(nextProps, nextState) {}
UNSAFE_componentWillReceiveProps(nextProps) {}
}
複製代碼
react 版本 17 將棄用幾個類組件 API 生命週期:componentWillMount
,componentWillReceiveProps
和componentWillUpdate
。
class Button extends react.Component {
constructor(props) {
super(props)
this.handleClick1 = this.handleClick1.bind(this)
}
//方式1:在構造函數中使用bind綁定this,官方推薦的綁定方式,也是性能最好的方式
handleClick1() {
console.log('this is:', this)
}
//方式2:在調用的時候使用bind綁定this
handleClick2() {
console.log('this is:', this)
}
//方式3:在調用的時候使用箭頭函數綁定this
// 方式2和方式3會有性能影響而且當方法做爲屬性傳遞給子組件的時候會引發重渲問題
handleClick3() {
console.log('this is:', this)
}
//方式4:使用屬性初始化器語法綁定this,須要babel轉義
handleClick4 = () => {
console.log('this is:', this)
}
render() {
return (
<div> <button onClick={this.handleClick1}>Click me</button> <button onClick={this.handleClick2.bind(this)}>Click me</button> <button onClick={() => this.handleClick3}>Click me</button> <button onClick={this.handleClick4}>Click me</button> </div>
)
}
}
複製代碼
爲何直接調用方法會報錯
class Foo extends React.Component {
handleClick() {
this.setState({ xxx: aaa })
}
render() {
return <button onClick={this.handleClick.bind(this)}>Click me</button>
}
}
複製代碼
會被 babel 轉化成
React.createElement(
'button',
{
onClick: this.handleClick
},
'Click me'
)
複製代碼
react 實現了一個「合成事件」層(synthetic event system
),這抹平了各個瀏覽器的事件兼容性問題。全部事件均註冊到了元素的最頂層-document 上,「合成事件」會以事件委託(event delegation
)的方式綁定到組件最上層,而且在組件卸載(unmount
)的時候自動銷燬綁定的事件。
import classNames from 'classnames'
class Button extends react.Component {
//參數傳參與校驗
static propTypes = {
type: PropTypes.oneOf(['success', 'normal']),
onClick: PropTypes.func
}
static defaultProps = {
type: 'normal'
}
handleClick() {}
render() {
let { className, type, children, ...other } = this.props
const classes = classNames(
className,
'prefix-button',
'prefix-button-' + type
)
return (
<span className={classes} {...other} onClick={() => this.handleClick}> {children} </span>
)
}
}
複製代碼
純展現型的,不須要維護 state 和生命週期,則優先使用 Function Component
import react from 'react'
function MyComponent(props) {
let { firstName, lastName } = props
return (
<div> <img src="avatar.png" className="profile" /> <h3>{[firstName, lastName].join(' ')}</h3> </div> ) } 複製代碼
會被 babel 轉義成
return React.createElement(
'div',
null,
React.createElement('img', { src: 'avatar.png', className: 'profile' }),
React.createElement('h3', null, [firstName, lastName].join(' '))
)
複製代碼
那麼,React.createElement
是在作什麼?看下相關部分代碼:
var ReactElement = function(type, key, ref, self, source, owner, props) {
var element = {
// This tag allow us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner
}
// ...
return element
}
ReactElement.createElement = function(type, config, children) {
// ...
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props
)
}
複製代碼
React.createElement()來構建 React 元素的。它接受三個參數,第一個參數type能夠是一個標籤名。如 div、span,或者 React 組件。第二個參數props爲傳入的屬性。第三個以及以後的參數children,皆做爲組件的子組件。
createElement
函數對 key 和 ref 等特殊的 props 進行處理,並獲取 defaultProps
對默認 props 進行賦值,而且對傳入的孩子節點進行處理,最終構形成一個 reactElement
對象(所謂的虛擬 DOM)。 reactDOM.render
將生成好的虛擬 DOM 渲染到指定容器上,其中採用了批處理、事務等機制而且對特定瀏覽器進行了性能優化,最終轉換爲真實 DOM。
ES6 class
定義一個純組件(PureComponent
)組件須要維護 state 或使用生命週期方法,則優先使用 PureComponent
class MyComponent extends react.Component {
render() {
let { name } = this.props
return <h1>Hello, {name}</h1>
}
}
複製代碼
PureComponent
Component
& PureComponent
這兩個類基本相同,惟一的區別是 PureComponent
的原型上多了一個標識,shallowEqual
(淺比較),來決定是否更新組件,淺比較相似於淺複製,只會比較第一層。使用 PureComponent
至關於省去了寫 shouldComponentUpdate
函數
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
}
複製代碼
這是檢查組件是否須要更新的一個判斷,ctor 就是你聲明的繼承自 Component
or PureComponent
的類,他會判斷你是否繼承自 PureComponent,若是是的話就
shallowEqual
比較 state 和 props。
React 中對比一個 ClassComponent
是否須要更新,只有兩個地方。一是看有沒有 shouldComponentUpdate
方法,二就是這裏的 PureComponent
判斷
Immutablejs
Immutable.js
是 Facebook 在 2014 年出的持久性數據結構的庫,持久性指的是數據一旦建立,就不能再被更改,任何修改或添加刪除操做都會返回一個新的 Immutable
對象。可讓咱們更容易的去處理緩存、回退、數據變化檢測等問題,簡化開發。而且提供了大量的相似原生 JS 的方法,還有 Lazy Operation
的特性,徹底的函數式編程。
import { Map } from 'immutable'
const map1 = Map({ a: { aa: 1 }, b: 2, c: 3 })
const map2 = map1.set('b', 50)
map1 !== map2 // true
map1.get('b') // 2
map2.get('b') // 50
map1.get('a') === map2.get('a') // true
複製代碼
能夠看到,修改 map1 的屬性返回 map2,他們並非指向同一存儲空間,map1 聲明瞭只有,全部的操做都不會改變它。
ImmutableJS
提供了大量的方法去更新、刪除、添加數據,極大的方便了咱們操縱數據。除此以外,還提供了原生類型與 ImmutableJS
類型判斷與轉換方法:
import { fromJS, isImmutable } from 'immutable'
const obj = fromJS({
a: 'test',
b: [1, 2, 4]
}) // 支持混合類型
isImmutable(obj) // true
obj.size() // 2
const obj1 = obj.toJS() // 轉換成原生 `js` 類型
複製代碼
ImmutableJS
最大的兩個特性就是: immutable data structures
(持久性數據結構)與 structural sharing
(結構共享),持久性數據結構保證數據一旦建立就不能修改,使用舊數據建立新數據時,舊數據也不會改變,不會像原生 js 那樣新數據的操做會影響舊數據。而結構共享是指沒有改變的數據共用一個引用,這樣既減小了深拷貝的性能消耗,也減小了內存。
左邊是舊值,右邊是新值,我須要改變左邊紅色節點的值,生成的新值改變了紅色節點到根節點路徑之間的全部節點,也就是全部青色節點的值,舊值沒有任何改變,其餘使用它的地方並不會受影響,而超過一大半的藍色節點仍是和舊值共享的。在 ImmutableJS
內部,構造了一種特殊的數據結構,把原生的值結合一系列的私有屬性,建立成 ImmutableJS
類型,每次改變值,先會經過私有屬性的輔助檢測,而後改變對應的須要改變的私有屬性和真實值,最後生成一個新的值,中間會有不少的優化,因此性能會很高。
higher order component
)高階組件是一個以組件爲參數並返回一個新組件的函數。HOC 運行你重用代碼、邏輯和引導抽象。
function visible(WrappedComponent) {
return class extends Component {
render() {
const { visible, ...props } = this.props
if (visible === false) return null
return <WrappedComponent {...props} /> } } } 複製代碼
上面的代碼就是一個 HOC 的簡單應用,函數接收一個組件做爲參數,並返回一個新組件,新組建能夠接收一個 visible props,根據 visible 的值來判斷是否渲染 Visible。 最多見的還有 Redux 的 connect 函數。除了簡單分享工具庫和簡單的組合,HOC 最好的方式是共享 react 組件之間的行爲。若是你發現你在不一樣的地方寫了大量代碼來作同一件事時,就應該考慮將代碼重構爲可重用的 HOC。 下面就是一個簡化版的 connect 實現:
export const connect = (
mapStateToProps,
mapDispatchToProps
) => WrappedComponent => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
constructor() {
super()
this.state = {
allProps: {}
}
}
componentWillMount() {
const { store } = this.context
this._updateProps()
store.subscribe(() => this._updateProps())
}
_updateProps() {
const { store } = this.context
let stateProps = mapStateToProps
? mapStateToProps(store.getState(), this.props)
: {}
let dispatchProps = mapDispatchToProps
? mapDispatchToProps(store.dispatch, this.props)
: {}
this.setState({
allProps: {
...stateProps,
...dispatchProps,
...this.props
}
})
}
render() {
return <WrappedComponent {...this.state.allProps} /> } } return Connect } 複製代碼
代碼很是清晰,connect 函數其實就作了一件事,將 mapStateToProps
和 mapDispatchToProps
分別解構後傳給原組件,這樣咱們在原組件內就能夠直接用 props
獲取 state
以及 dispatch
函數了。
某些頁面須要記錄用戶行爲,性能指標等等,經過高階組件作這些事情能夠省去不少重複代碼。
function logHoc(WrappedComponent) {
return class extends Component {
componentWillMount() {
this.start = Date.now()
}
componentDidMount() {
this.end = Date.now()
console.log(
`${WrappedComponent.dispalyName} 渲染時間:${this.end - this.start} ms`
)
console.log(`${user}進入${WrappedComponent.dispalyName}`)
}
componentWillUnmount() {
console.log(`${user}退出${WrappedComponent.dispalyName}`)
}
render() {
return <WrappedComponent {...this.props} /> } } } 複製代碼
function auth(WrappedComponent) {
return class extends Component {
render() {
const { visible, auth, display = null, ...props } = this.props
if (visible === false || (auth && authList.indexOf(auth) === -1)) {
return display
}
return <WrappedComponent {...props} /> } } } 複製代碼
基於上面的雙向綁定的例子,咱們再來一個表單驗證器,表單驗證器能夠包含驗證函數以及提示信息,當驗證不經過時,展現錯誤信息:
function validateHoc(WrappedComponent) {
return class extends Component {
constructor(props) {
super(props)
this.state = { error: '' }
}
onChange = event => {
const { validator } = this.props
if (validator && typeof validator.func === 'function') {
if (!validator.func(event.target.value)) {
this.setState({ error: validator.msg })
} else {
this.setState({ error: '' })
}
}
}
render() {
return (
<div> <WrappedComponent onChange={this.onChange} {...this.props} /> <div>{this.state.error || ''}</div> </div> ) } } } 複製代碼
const validatorName = {
func: (val) => val && !isNaN(val),
msg: '請輸入數字'
}
const validatorPwd = {
func: (val) => val && val.length > 6,
msg: '密碼必須大於6位'
}
<HOCInput validator={validatorName} v_model="name"></HOCInput>
<HOCInput validator={validatorPwd} v_model="pwd"></HOCInput>
複製代碼
render props
一種在 React 組件之間使用一個值爲函數的 prop 共享代碼的簡單技術 具備 render prop 的組件接受一個函數,該函數返回一個 React 元素並調用它而不是實現本身的渲染邏輯。
<DataProvider render={data => <h1>Hello {data.target}</h1>} />
複製代碼
setState
數據管理// Wrong 此代碼不會從新渲染組件,構造函數是惟一可以初始化 this.state 的地方。
this.state.comment = 'Hello'
// Correct 應當使用 setState():
this.setState({ comment: 'Hello' })
複製代碼
組件生命週期中或者 react 事件綁定中,setState 是經過異步更新的,在延時的回調或者原生事件綁定的回調中調用 setState 不必定是異步的。
// Wrong
this.setState({
counter: this.state.counter + this.props.increment
})
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}))
複製代碼
原生事件綁定不會經過合成事件的方式處理,會進入更新事務的處理流程。`setTimeout` 也同樣,在 `setTimeout` 回調執行時已經完成了原更新組件流程,不會放入 `dirtyComponent` 進行異步更新,其結果天然是同步的。
setState
原理setState 並無直接操做去渲染,而是執行了一個 updateQueue
(異步 updater 隊列),
setState( stateChange ) {
Object.assign( this.state, stateChange );
//合併接收到的state||stateChange改變的state(setState接收到的參數)
renderComponent( this );//調用render渲染組件
}
複製代碼
這種實現,每次調用 setState
都會更新 state 並立刻渲染一次(不符合其更新優化機制),因此咱們要合併 setState
。
具體能夠閱讀源碼 ReactUpdateQueue.js
react
中的事務實現待完善 這塊看的還有點懵圈 React 源碼解析(三):詳解事務與更新隊列 React 中的 Transaction React 的事務機制
ErrorBoundary
、Suspense
和 Fragment
Error Boundaries
react 16 提供了一個新的錯誤捕獲鉤子 componentDidCatch(error, errorInfo)
, 它能將子組件生命週期裏所拋出的錯誤捕獲, 防止頁面全局崩潰。demo componentDidCatch
並不會捕獲如下幾種錯誤
lazy、Suspence
延遲加載組件lazy
須要跟 Suspence
配合使用,不然會報錯。
lazy
其實是幫助咱們實現代碼分割 ,相似 webpack 的 splitchunk
的功能。
Suspense
意思是能暫停當前組件的渲染, 當完成某件事之後再繼續渲染。簡單來講就是減小首屏代碼的體積,提高性能。
import react, { lazy, Suspense } from 'react'
const OtherComponent = lazy(() => import('./OtherComponent'))
function MyComponent() {
return (
<Suspense fallback={<div>loading...</div>}> <OtherComponent /> </Suspense>
)
}
複製代碼
一種簡單的預加載思路, 可參考 preload
const OtherComponentPromise = import('./OtherComponent')
const OtherComponent = react.lazy(() => OtherComponentPromise)
複製代碼
Fragments(v16.2.0)
Fragments 容許你將子列表分組,避免向 DOM 添加額外的節點。
render() {
return (
<> <ChildA /> <ChildB /> <ChildC /> </> ); } 複製代碼
react Fiber
架構分析react-fiber
是爲了加強動畫、佈局、移動端手勢領域的適用性,最重要的特性是對頁面渲染的優化: 容許將渲染方面的工做拆分爲多段進行。
react Fiber
架構解決了什麼問題react-fiber
能夠爲咱們提供以下幾個功能:
Fiber
如何作到異步渲染 Virtual Dom
和 Diff
算法衆所周知,畫面每秒鐘更新 60 次,頁面在人眼中顯得流暢,無明顯卡頓。每秒 60 次,即 16ms 要更新一次頁面,若是更新頁面消耗的時間不到 16ms,那麼在下一次更新時機來到以前會剩下一點時間執行其餘的任務,只要保證及時在 16ms 的間隔下更新界面就徹底不會影響到頁面的流暢程度。fiber 的核心正是利用了 60 幀原則,實現了一個基於優先級和 requestIdleCallback 的循環任務調度算法。
function fiber(剩餘時間) {
if (剩餘時間 > 任務所需時間) {
作任務
} else {
requestIdleCallback(fiber)
// requestIdleCallback 是瀏覽器提供的一個 api,可讓瀏覽器在空閒的時候執行回調,
// 在回調參數中能夠獲取到當前幀剩餘的時間,fiber 利用了這個參數,
// 判斷當前剩下的時間是否足夠繼續執行任務,
// 若是足夠則繼續執行,不然暫停任務,
// 並調用 requestIdleCallback 通知瀏覽器空閒的時候繼續執行當前的任務
}
}
複製代碼
react hooks
在 react 16.7 以前, react 有兩種形式的組件, 有狀態組件(類)和無狀態組件(函數)。 官方解釋: hook 是 React 16.8 的新增特性。它可讓你在不編寫 class 的狀況下使用 state 以及其餘的 React 特性。 我的理解:讓傳統的函數組件 function component 有內部狀態 state 的函數 function,簡單來講就是 hooks 讓函數組件有了狀態,能夠徹底替代 class。
接下來梳理 Hooks 中最核心的 2 個 api, useState
和 useEffect
useState
useState 是一個鉤子,他能夠爲函數式組件增長一些狀態,而且提供改變這些狀態的函數,同時它接收一個參數,這個參數做爲狀態的默認值。
const [count, setCount] = useState(initialState)
複製代碼
使用 Hooks 相比以前用 class 的寫法最直觀的感覺是更爲簡潔
function App() {
const [count, setCount] = useState(0)
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div>
)
}
複製代碼
useEffect(fn)
在每次 render 後都會執行這個鉤子。能夠將它當成是 componentDidMount
、componentDidUpdate``、componentWillUnmount
的合集。所以使用 useEffect
比以前優越的地方在於:
能夠避免在 componentDidMount
、componentDidUpdate
書寫重複的代碼; 能夠將關聯邏輯寫進一個 useEffect
(在之前得寫進不一樣生命週期裏);
使用 react.createElement
或 JSX 編寫 react 組件,實際上全部的 JSX 代碼最後都會轉換成 react.createElement(...)
,Babel 幫助咱們完成了這個轉換的過程。
createElement 函數對 key 和 ref 等特殊的 props 進行處理,並獲取 defaultProps
對默認 props 進行賦值,而且對傳入的孩子節點進行處理,最終構形成一個 reactElement
對象(所謂的虛擬 DOM)。
reactDOM.render
將生成好的虛擬 DOM 渲染到指定容器上,其中採用了批處理、事務等機制而且對特定瀏覽器進行了性能優化,最終轉換爲真實 DOM。
即 reactElementelement
對象,咱們的組件最終會被渲染成下面的結構:
`type`:元素的類型,能夠是原生 html 類型(字符串),或者自定義組件(函數或 class)
`key`:組件的惟一標識,用於 Diff 算法,下面會詳細介紹
`ref`:用於訪問原生 dom 節點
`props`:傳入組件的 props,chidren 是 props 中的一個屬性,它存儲了當前組件的孩子節點,能夠是數組(多個孩子節點)或對象(只有一個孩子節點)
`owner`:當前正在構建的 Component 所屬的 Component
`self`:(非生產環境)指定當前位於哪一個組件實例
`_source`:(非生產環境)指定調試代碼來自的文件(fileName)和代碼行數(lineNumber)
複製代碼
當組件狀態 state 有更改的時候,react 會自動調用組件的 render 方法從新渲染整個組件的 UI。 固然若是真的這樣大面積的操做 DOM,性能會是一個很大的問題,因此 react 實現了一個 Virtual DOM
,組件 DOM 結構就是映射到這個 Virtual DOM
上,react 在這個 Virtual DOM
上實現了一個 diff 算法,當要從新渲染組件的時候,會經過 diff 尋找到要變動的 DOM 節點,再把這個修改更新到瀏覽器實際的 DOM 節點上,因此實際上不是真的渲染整個 DOM 樹。這個 Virtual DOM
是一個純粹的 JS 數據結構,因此性能會比原生 DOM 快不少。
react
是如何防止 XSS
的reactElement
對象還有一個$$typeof
屬性,它是一個 Symbol 類型的變量Symbol.for('react.element')
,當環境不支持 Symbol 時,$$typeof
被賦值爲 0xeac7
。 這個變量能夠防止 XSS。若是你的服務器有一個漏洞,容許用戶存儲任意 JSON 對象, 而客戶端代碼須要一個字符串,這可能爲你的應用程序帶來風險。JSON 中不能存儲 Symbol
類型的變量,而 react 渲染時會把沒有\$\$typeof
標識的組件過濾掉。
diff
算法傳統的 diff
算法經過循環遞歸對節點一次對比,效率很低,算法複雜度達到 O(n^3),其中 n 是樹中節點的總數,React 經過制定大膽的策略,將 O(n^3) 複雜度的問題轉換成 O(n) 複雜度的問題。
diff
策略:
diff
算法比較新舊節點的時候,比較只會在同層級比較,不會跨層級比較基於以上三個前提策略,React 分別對 tree diff
、component diff
以及 element diff
進行算法優化,事實也證實這三個前提策略是合理且準確的,它保證了總體界面構建的性能。 簡單的講就是:
具體能夠參考React 源碼剖析系列 - 難以想象的 react diff
tree diff
進行算法優化;component diff
進行算法優化;element diff
進行算法優化;建議,在開發組件時,保持穩定的 DOM 結構會有助於性能的提高; 建議,在開發過程當中,儘可能減小相似將最後一個節點移動到列表首部的操做,當節點數量過大或更新操做過於頻繁時,在必定程度上會影響 React 的渲染性能。
待補充
在使用 class Component
進行開發的時候,咱們可使用 shouldComponentUpdate
來減小沒必要要的渲染,那麼在使用 react hooks
後,咱們如何實現這樣的功能呢?
解決方案:React.memo
和useMemo
對於這種狀況,react 固然也給出了官方的解決方案,就是使用 React.memo 和 useMemo。
React.memo
React.momo 其實並非一個 hook,它其實等價於 PureComponent,可是它只會對比 props。使用方式以下(用上面的例子):
import React, { useState } from 'react'
export const Count = React.memo(props => {
const [data, setData] = useState({
count: 0,
name: 'cjg',
age: 18
})
const handleClick = () => {
const { count } = data
setData({
...data,
count: count + 1
})
}
return <button onClick={handleClick}>count:{data.count}</button>
})
複製代碼
useMemo 它的用法其實跟 useEffects 有點像,咱們直接看官方給的例子
function Parent({ a, b }) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a={a} />, [a])
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b={b} />, [b])
return (
<> {child1} {child2} </> ) } 複製代碼
從例子能夠看出來,它的第二個參數和 useEffect 的第二個參數是同樣的,只有在第二個參數數組的值發生變化時,纔會觸發子組件的更新。
當一個組件的 props 或 state 變動,React 會將最新返回的元素與以前渲染的元素進行對比,以此決定是否有必要更新真實的 DOM,當它們不相同時 React 會更新該 DOM。
即便 React 只更新改變了的 DOM 節點,從新渲染仍然花費了一些時間。在大部分狀況下它並非問題,可是若是渲染的組件很是多時,就會浮現性能上的問題,咱們能夠經過覆蓋生命週期方法 shouldComponentUpdate 來進行提速。
shouldComponentUpdate 方法會在從新渲染前被觸發。其默認實現老是返回 true,若是組件不須要更新,能夠在 shouldComponentUpdate 中返回 false 來跳過整個渲染過程。其包括該組件的 render 調用以及以後的操做。
shouldComponentUpdate(nextProps, nextState) {
return nextProps.next !== this.props.next
}
複製代碼
React 16.5 增長了對新的開發者工具 DevTools 性能分析插件的支持。 此插件使用 React 實驗性的 Profiler API 來收集有關每一個組件渲染的用時信息,以便識別 React 應用程序中的性能瓶頸。 它將與咱們即將推出的 time slicing(時間分片) 和 suspense(懸停) 功能徹底兼容。
Store
:保存數據的地方,你能夠把它當作一個容器,整個應用只能有一個 Store
。
State
:Store
對象包含全部數據,若是想獲得某個時點的數據,就要對 Store
生成快照,這種時點的數據集合,就叫作 State
。
Action
:State
的變化,會致使 View 的變化。可是,用戶接觸不到 State,只能接觸到 View。因此,State 的變化必須是 View 致使的。Action 就是 View 發出的通知,表示 State 應該要發生變化了。
Action Creator
:View 要發送多少種消息,就會有多少種 Action
。若是都手寫,會很麻煩,因此咱們定義一個函數來生成 Action,這個函數就叫 Action Creator
。
Reducer
:Store
收到 Action
之後,必須給出一個新的 State
,這樣 View 纔會發生變化。這種 State
的計算過程就叫作 Reducer
。Reducer
是一個函數,它接受 Action 和當前 State
做爲參數,返回一個新的 State
。
dispatch
:是 View
發出 Action
的惟一方法。
而後咱們過下整個工做流程:
首先,用戶(經過 View
)發出 Action
,發出方式就用到了 dispatch
方法。
而後,Store
自動調用 Reducer
,而且傳入兩個參數:當前 State
和收到的 Action
,Reducer
會返回新的 State
State
一旦有變化,Store
就會調用監聽函數,來更新 View
。
到這兒爲止,一次用戶交互流程結束。能夠看到,在整個流程中數據都是單向流動的,這種方式保證了流程的清晰。
redux
單向數據流架構如何設計待完善
redux
中間件Redux 的中間件提供的是位於 action 被髮起以後,到達 reducer 以前的擴展點,換而言之,本來 view -> action -> reducer -> store 的數據流加上中間件後變成了 view -> action -> middleware -> reducer -> store ,在這一環節咱們能夠作一些 「反作用」 的操做,如 異步請求、打印日誌等。
redux 中間件經過改寫 store.dispatch 方法實現了 action -> reducer 的攔截,從上面的描述中能夠更加清晰地理解 redux 中間件的洋蔥圈模型:
中間件A -> 中間件B-> 中間件C-> 原始 dispatch -> 中間件C -> 中間件B -> 中間件A
複製代碼
這也就提醒咱們使用中間件時須要注意這個中間件是在何時 「搞事情」 的,好比 redux-thunk 在執行 next(action) 前就攔截了類型爲 function 的 action,而 redux-saga 就在 next(action) 纔會觸發監聽 sagaEmitter.emit(action), 並不會攔截已有 action 到達 reducer。