Vue、React和Angular成爲了前端工做者最經常使用的三大框架。分析其源碼,對咱們技術的提高有着相當重要的做用,咱們先分析React源碼,本篇文章將從其最經常使用的API提及html
一、React源碼地址:從github上下載源碼前端
二、React官方文檔:準備好文檔,便於參考react
根據官方文檔以及源碼,畫出一張思惟導圖git
這些API將是這篇文章着重介紹的React APIgithub
在React中,咱們常常編寫JSX,JSX和JS最大的不一樣就是:在JSX中,能夠寫HTML的代碼。那麼React是如何解析JSX的呢?這裏面就涉及到了JSX向JS的轉換,在React中,JSX經過Babel轉換成JS去編譯。接下來,咱們來研究Babel是如何將JSX轉換成JS的。promise
首先推薦一個測試工具:Babel轉換工具bash
在JSX中先寫一對div標籤微信
<div></div>
複製代碼
它經過Babel轉換成了這樣babel
React.createElement("div", null)
複製代碼
逐步增長標籤內的內容框架
轉換前 | 轉換後 |
---|---|
<div>我被轉換了</div> |
React.createElement("div", null, '我被轉換了') |
<div key="1">我被轉換了</div> |
React.createElement("div", {key: "1"}, 我被轉換了) |
<div key="1" id="2">我被轉換了</div> |
React.createElement("div",{key: "1",id: "2"},"我被轉換了") |
<div><span>001</span><span>002</span></div> |
React.createElement("div", null, React.createElement("span", null),React.createElement("span",null)) |
經過上面這些狀況,能夠總結出:
React.createElement
函數建立 ,它接收三個參數(標籤名稱,標籤屬性,標籤內容)null
,第三個參數會根據標籤內容判斷,因此可能會出現第四個參數、第五個參數...在React中,咱們常常要封裝自定義組件,那麼對自定義組件和原生DOM節點的解析有何不一樣?
// Test組件時一個自定義組件
function Test() {
return <a>我是一個自定義組件</a>
}
// 使用Test自定義組件
<Test>測試</Test>
複製代碼
被Babel編譯後
function Test() {
return React.createElement("a", null, "\u6211\u662F\u4E00\u4E2A\u81EA\u5B9A\u4E49\u7EC4\u4EF6");
}
React.createElement(Test, null, "\u6D4B\u8BD5");
複製代碼
能夠看到:
React.createElement
函數中的第一個參數是一個變量,而不是像上面同樣是一個字符串,這是由於Babel區分了自定義組件和原生DOM節點在React中,常用JSX,咱們也首先要了解JSX向JS轉換的原理,其次才能作其餘部分的事情。
咱們要知道的第一個API就是ReactElment,它的源碼的文件名是ReactElement.js。上面的思惟導圖就是這個.js文件大概所要闡述的,中心主題是咱們所說的ReactElement,第二層節點是全部的方法,第三層節點是方法所接收的參數,第四層及之後是參數上的屬性,每一個方法最後會返回一個方法,虛線是最後返回ReactElemnt方法的部分。有了這樣一個思路之後,再看源碼就不會感受到那麼難了。
我摘取了如下幾個部分
1.這裏面使用了不少Object構造函數中的方法,好比getPropertyDescriptor()
、defineProperty()
等等,這須要咱們去了解這些方法的用途。
2.在createElemnt
方法中,有這樣一段代碼
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
if (__DEV__) {
if (Object.freeze) {
Object.freeze(childArray);
}
}
props.children = childArray;
}
複製代碼
createElement
方法中會接收三個參數,這部分代碼是處理第三個參數children
的,咱們在3.1中介紹JSX
向JS
中轉換說到的createElement
方法其實就是它,createElement
方法接收的參數個數由父節點的內容所控制(參見3.1),因此咱們看到的childrenLength
的計算-2
,就是減掉了這個方法接收的前兩個參數,而後呢,根據它的長度判斷chilren
的值,最後傳給了props
的children
屬性
3.對defaultProps的處理
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
複製代碼
這部分也是在createElement
方法中處理的,它是對第一個參數type
中的defaultProps
屬性的處理,在咱們本身封裝組件時,可能會讓一些屬性有默認值,在父組件中使用時,會判斷父組件是否給子組件傳值,若是沒有,就會使用defaultProps
中定義的默認值
4.ReactElement方法接收了七個參數,並最終返回了element。剩下的部分大多和這些思想一致,就不一一累述。
學習源碼是一個枯燥的過程,而這僅僅是一個API介紹,是一個開始,咱們要作的就是學習源碼的思考問題的方式等,讓本身思考問題更加全面,學會寫更優秀的代碼。我同時也推薦先畫思惟導圖這種方式,理解,再深刻研究每一個方法的用途,這麼寫代碼的目的。
Component源碼是在ReactBaseClasses文件中,一樣在這個文件中存在的還有一個PureComponent源碼,最終在文件中export {PureComponent, Component}
。這部分代碼不是很長,咱們依然挑出重點的部分進行分享:
Component.prototype.setState = function(partialState, callback) {
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.',
);
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
複製代碼
這部分代碼就是咱們經常使用的this.setState
方法的源碼部分,它是存在於Component的原型鏈上的方法,接受了兩個參數,第一個參數是要更新state的部分,第二個參數是一個回調函數,走進函數,首先是要判斷partialState
的類型,若是類型不正確,將會報錯;正式更新state的部分是this.updater.enqueueSetState(this, partialState, callback, 'setState')
,這裏面調用了updater
中的enqueueSetState
方法,它接收了四個參數,那麼它是如何更新state的呢?咱們先留一個懸念...
一、ref
ref的使用有三種方式:
① stringRef
② methodRef(function)
③ createRef
import React, {Component} from 'react';
export default class Ref extends Component {
constructor (props) {
super();
this.objRef = React.createRef()
this.state = {
ref: '初始化'
}
}
componentDidMount() {
setTimeout(() => {
this.refs.stringRef.textContent = '我加載成功了'
this.methodRef.textContent = '我也加載成功了'
this.objRef.current.textContent = '我一樣加載成功了'
}, 2000)
}
render() {
const { ref } = this.state;
return (
<div>
<h3 ref="stringRef">{ref}</h3>
<h3 ref={ele => (this.methodRef = ele)}>{ref}</h3>
<h3 ref={this.objRef}>{ref}</h3>
</div>
)
}
}
複製代碼
二、forward-ref
forward-ref的出現是爲了解決HOC組件傳遞ref的問題
import React, {Component} from 'react';
const Target = React.forwardRef((props, ref) => {
return (
<input ref={ref} />
)
})
export default class Forward extends Component {
constructor(props) {
super();
this.ref = React.createRef();
this.state = {
}
}
componentDidMount(){
this.ref.current.value = 'forward ref';
}
render() {
return <Target ref={this.ref} />
}
}
複製代碼
對於ref的三種使用方式,在源碼中有所體現的是第三種。在源碼中,有一個createRef函數,它聲明瞭refObject變量,裏面存在current屬性,默認值爲空,最後return refObject,在咱們使用的時候,current的值就是當前使用的節點名稱。
forward-ref返回了一個對象,裏面存在$$typeof和render函數。對於forward-ref的使用,它解決了HOC組件傳遞ref的問題,使用的方法如上面第二段代碼所示。若是使用stringRef沒法將ref做爲參數傳遞,因此有了createRef方法
// 子組件
import React, {Component} from 'react';
import PropTypes from 'prop-types';
export default class ContextChild extends Component {
render() {
return (
<div>
<h3>childContext: {this.context.value}</h3>
</div>
)
}
}
ContextChild.contextTypes = {
value: PropTypes.string
}
// 父組件
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import ContextChild from './context-child';
export default class Context extends Component {
constructor (props) {
super();
this.state = {
childContext: 'childContext',
}
}
getChildContext() {
return {value: this.state.childContext}
}
render() {
const { childContext } = this.state;
return (
<div>
<div>
<input
value={childContext}
onChange={e => {this.setState({childContext: e.target.value})}}
/>
</div>
<ContextChild />
</div>
)
}
}
Context.childContextTypes = {
value: PropTypes.string
}
複製代碼
這樣,當
Input
輸入框值改變時,子組件中獲取的
this.context.value
也會隨之改變,這裏面,須要注意的是要規定context的類型,若是父子組件context類型不統一,是不會得到上面的結果的
import React, {Component} from 'react';
import PropTypes from 'prop-types';
const { Provider, Consumer } = React.createContext()
export default class Context extends Component {
constructor (props) {
super();
this.state = {
create: 'create'
}
}
render() {
const { childContext, create } = this.state;
return (
<div>
<div>
<input
type="text"
value={create}
onChange={e => {this.setState({create: e.target.value})}}
/>
</div>
<Provider value={this.state.create}>
<Consumer>{value => <p>create: {value}</p>}</Consumer>
</Provider>
</div>
)
}
}
Context.childContextTypes = {
value: PropTypes.string
}
複製代碼
Provider和Consumer組件配合使用,Consumer組件能夠得到Provider組件上的value屬性的值,當input值改變時,Consumer組件裏面的值隨之改變
context部分只有createContext的源碼,它接收了defaultValue和calculateChangedBits(計算新老context的API變化的方法)兩個值,最後返回了context對象,下面分析context對象這一部分的源碼
const context: ReactContext<T> = {
$$typeof: REACT_CONTEXT_TYPE,
_calculateChangedBits: calculateChangedBits,
// As a workaround to support multiple concurrent renderers, we categorize
// some renderers as primary and others as secondary. We only expect
// there to be two concurrent renderers at most: React Native (primary) and
// Fabric (secondary); React DOM (primary) and React ART (secondary).
// Secondary renderers store their context values on separate fields.
_currentValue: defaultValue,
_currentValue2: defaultValue,
// Used to track how many concurrent renderers this context currently
// supports within in a single renderer. Such as parallel server rendering.
_threadCount: 0,
// These are circular
Provider: (null: any),
Consumer: (null: any),
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
context.Consumer = context;
複製代碼
上面代碼中
I. _currentValue: 記錄Provider組件上value屬性的值
II. context對象裏面擁有Provider、Consumer
III. _context: context中的context指的是上面的context對象
IV. context.Consumer等於自己的context對象,因此在使用Consumer組件時,可直接獲取context對象中_currentValue的值
// suspenseDemo.jsx
import React, { Suspense, lazy } from 'react'
const LazyComp = lazy(() => import('./suspenseChild.jsx'))
let data = ''
let result = ''
function requestData() {
if (data) return data
if (result) throw result
result = new Promise(resolve => {
setTimeout(() => {
data = 'success'
resolve()
}, 4000)
})
throw result
}
function SuspenseComp() {
return <p>{requestData()}</p>
}
export default SuspenseDemo => (
<Suspense fallback="loading data">
<SuspenseComp />
<LazyComp />
</Suspense>
)
// suspenseChild.jsx
import React, { Component } from 'react';
export default () => {
return <p>child</p>
}
複製代碼
效果圖:
Suspense組件在其下方組件返回promise結果以前,都展現fallback函數中的值,包裹在suspense中用lazy加載的組件也會在promise結果返回後與異步組件一同加載。Suspense是一個常量,lazy在react中接收一個方法,返回Thenable,Thenable是promise對象,最終返回LazyComponent
export function lazy<T, R>(ctor: () => Thenable<T, R>): LazyComponent<T> {
let lazyType = {
$$typeof: REACT_LAZY_TYPE,
_ctor: ctor,
// React uses these fields to store the result.
_status: -1,
_result: null,
};
return lazyType;
}
複製代碼
返回的LazyComponent中,存在lazyType對象,其中的_ctor屬性爲它接收的方法,_status屬性是記錄Thenable對象的狀態,_result爲返回的結果。
在react的官方網站上一個useState的實例
import React, { useState } from 'react';
const State = () => {
// 聲明一個新的叫作 「count」 的 state 變量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default State
複製代碼
效果圖
每次點擊按鈕的時候,上面的數字就會+1hooks是react16.8版本新增的特性,它可使咱們在不編寫class的狀況下使用state或者其餘的React特性。
在使用的時候,咱們要先引入使用的特性,像這樣:
import React, { useState } from 'react';
而後根據咱們想要實現的功能,去選擇對應的hook功能API,hook的源碼先定義了一個resolveDispatcher函數方法,該方法返回dispatcher,接下來定義每個hook的API並導出,設置其API接收的參數,並有對應的返回
這篇文章主要講解了部分React的基礎API以及它的使用方法和部分源碼解析,並無深刻。接下來,咱們會一點一點深刻react,接下來關於react的文章:
上述文章若有不對之處,還請你們指點出來,咱們共同進步~
最後,分享一下個人微信公衆號~