react首先是相似一個組件庫的js文件,包含view和controller的庫。javascript
react組件根據平臺自己能夠映射成原生控件和web dom。css
採用babel的編譯工具將jsx轉換成js來描述對應的元素。html
Portal前端
Portal 提供了一種將子節點渲染到存在於父組件之外的 DOM 節點的優秀的方案。html5
一個 portal 的典型用例是當父組件有 overflow: hidden
或 z-index
樣式時,但你須要子組件可以在視覺上「跳出」其容器。例如,對話框、懸浮卡以及提示框:java
ReactDOM.createPortal(child, container)
vitrualDomreact
Babel 會把 JSX 轉譯成一個名爲 React.createElement()
函數調用。webpack
React.createElement()
會預先執行一些檢查,以幫助你編寫無錯代碼,但實際上它建立了一個這樣的對象web
const element = { type: 'h1', props: { className: 'xxxClass', children: 'Hello, world!' } };
createElement函數內部作的操做很簡單,將props和子元素進行處理後返回一個ReactElement對象算法
接下來調用:
render: ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback)。 將目標元素插入指定節點container
這些對象被稱爲 「React 元素」。它們描述了你但願在屏幕上看到的內容。React 經過讀取這些對象,而後使用它們來構建 DOM 以及保持隨時更新。
想要將一個 React 元素渲染到根 DOM 節點中,只需把它們一塊兒傳入 ReactDOM.render()。
React DOM 會將元素和它的子元素與它們以前的狀態進行比較,並只會進行必要的更新來使 DOM 達到預期的狀態。
ReactDOM.render將生成好的虛擬DOM渲染到指定容器上,其中採用了批處理、事務等機制而且對特定瀏覽器進行了性能優化,最終轉換爲真實DOM。
與瀏覽器的 DOM 元素不一樣,React 元素是建立開銷極小的普通對象。React DOM 會負責更新 DOM 來與 React 元素保持一致。
全部 React 組件都必須像純函數同樣保護它們的 props 不被更改。
若是是首次渲染,VitrualDom不具備任何優點,甚至它要進行更多的計算,消耗更多的內存。
VitrualDom的優點在於React的Diff算法和批處理策略,React在頁面更新以前,提早計算好了如何進行更新和渲染DOM。
isValidElement()
驗證對象是否爲 React 元素,返回值爲 true
或 false
。
diff算法:
先找到新舊樹的前置元素和後置元素,並分別用前置和後置指針指向。
一、若是新舊樹前置元素相同,前置指針日後移;
二、若是碰到前置元素不相同,則找後置元素,若是後置元素相同,則後置指針前移。
三、若是前置和後置元素都不相同,則比較舊樹的前置元素和新樹的後置元素,若是相同,分別挪動舊樹的前置指針和新樹的後置指針。
四、若是舊樹的前置元素和新樹的後置元素不相同,則比較舊樹的後置元素和新樹的前置元素,若是相同,分別挪動舊樹的後置指針和新樹的前置指針。
五、若是都不相同,使用數組P記錄下舊樹中前置指針和後置指針之間的元素在新樹的下標,若是在舊樹中找不到,表示新增,下標爲-1。找出P數組中的LIS序列。
重新樹的尾置指針遍歷新樹。若是新樹元素在新樹中的下標在LIS中存在,則不移動,若是不存在,則移動
diff算法,只對比同級元素
如何防止xss攻擊:
React渲染時會把沒有$$typeof標識,以及規則校驗不經過的組件過濾掉。 防止xss攻擊
無狀態組件:使用無狀態函數構建的組件成爲無狀態組件,只傳入props,context兩個參數,不存在state,沒有生命週期方法。無狀態組件在調用時不會建立新實例,避免了沒必要要的檢查和內存分配。
Refs:
FancyButton
使用 React.forwardRef
來獲取傳遞給它的 ref
,而後轉發到它渲染的 DOM button
:
const FancyButton = React.forwardRef((props, ref) => ( <button ref={ref} className="FancyButton"> {props.children} </button> )); // 你能夠直接獲取 DOM button 的 ref: const ref = React.createRef(); <FancyButton ref={ref}>Click me!</FancyButton>;
Fragments
Fragments 容許你將子列表分組,而無需向 DOM 添加額外節點。
還有一種新的短語法可用於聲明它們,但還沒有獲得全部流行工具的支持。
React.PureComponent
來代替手寫 shouldComponentUpdate
。但它只進行淺比較,因此當 props 或者 state 某種程度是可變的話,淺比較會有遺漏,那你就不能使用它了。
Memo:
React.memo
爲高階組件。它與 React.PureComponent
很是類似,但它適用於函數組件,但不適用於 class 組件。
若是你的函數組件在給定相同 props 的狀況下渲染相同的結果,那麼你能夠經過將其包裝在 React.memo
中調用,以此經過記憶組件渲染結果的方式來提升組件的性能表現。這意味着在這種狀況下,React 將跳過渲染組件的操做並直接複用最近一次渲染的結果。
默認狀況下其只會對複雜對象作淺層對比,若是你想要控制對比過程,那麼請將自定義的比較函數經過第二個參數傳入來實現。
此方法僅做爲性能優化的方式而存在。但請不要依賴它來「阻止」渲染,由於這會產生 bug。
與 class 組件中 shouldComponentUpdate()
方法不一樣的是,若是 props 相等,areEqual
會返回 true
;若是 props 不相等,則返回 false
。這與 shouldComponentUpdate
方法的返回值相反。
function MyComponent(props) { /* 使用 props 渲染 */ } function areEqual(prevProps, nextProps) { /* 若是把 nextProps 傳入 render 方法的返回結果與 將 prevProps 傳入 render 方法的返回結果一致則返回 true, 不然返回 false */ } export default React.memo(MyComponent, areEqual);
React 作性能優化時最經常使用的就是 shouldComponentUpdate 方法,但它默 認返回 true,即始終會執行 render 方法,
而後作 Virtual DOM 比較,並得出是否須要作真實 DOM的更新,這裏每每會帶來不少不必的渲染。我們也能夠在 shouldComponentUpdate
中使用深拷貝和深比較來避免無必要的 render, 但深拷貝和深比較通常都是很是昂貴的選擇。
Immutable.js則提供了簡潔、高效的判斷數據是否變化的方法,只需 === 和 is 比較就能知 道是否須要執行 render,而這個操做幾乎零成本,因此能夠極大提升性能。
for (const key in nextProps) {
if (nextProps.hasOwnProperty(key) &&
!is(thisProps[key], nextProps[key])) { return true;}
}
return false;
cloneElement
React.cloneElement: 給指定組件傳遞props,以 element
元素爲樣板克隆並返回新的 React 元素。返回元素的 props 是將新的 props 與原始元素的 props 淺層合併後的結果。新的子元素將取代現有的子元素,而來自原始元素的 key
和 ref
將被保留。
React.cloneElement( element, [props], [...children] )
React.children: 獲取當前組件的子組件
合成事件:
React本身構造了合成事件對象SyntheticEvent,SyntheticEvent 實例將被傳遞給你的事件處理函數。
這是一個跨瀏覽器原生事件包裝器。 它具備與瀏覽器原生事件相同的接口,包括stopPropagation() 和 preventDefault() 等等,在全部瀏覽器中他們工做方式都相同。
若是由於某些緣由,當你須要使用瀏覽器的底層事件時,只須要使用 nativeEvent 屬性來獲取便可。
SyntheticEvent 是合併而來。這意味着 SyntheticEvent 對象可能會被重用,並且在事件回調函數被調用後,全部的屬性都會無效。
合成事件其實是組件掛載時註冊到document上的,事件冒泡到document被監聽到以後,會生成一個合成事件對象並分發給對應的handler去處理
事件捕獲:會優先調用結構樹最外層的元素上綁定的事件監聽器,而後依 次向內調用,一直調用到目標元素上的事件監聽器爲止。
事件冒泡:則與事件捕獲的表現相反,它會從目標元素向外傳播事件,由內而外直到最外層。
非受控組件
非受控組件:一個表單組件沒有 value props(單選按鈕和複選框對應的是 checked prop) 時,就能夠稱爲非受控組件。
它是一種反模式,它的值不受組件自身的 state 或 props 控制。一般, 須要經過爲其添加 ref prop 來訪問渲染後的底層 DOM 元素。
CSS Modules
classnames樣式庫:classNames({ 'btn': true, 'btn-pressed': this.state.isPressed, 'btn-over': !this.state.isPressed && this.state.isHovered,});
CSS Modules: 能最大化地結合現有 CSS 生態和 JavaScript 模塊化能力,其 API 很是簡潔。 發佈時依舊編譯出單獨的 JavaScript 和 CSS 文件。
如今,webpack css-loader 內置 CSS Modules 功能。
啓用 CSS Modules 的代碼以下:
// webpack.config.js
css?modules&localIdentName=[name]__[local]-[hash:base64:5]
加上 modules 即爲啓用,其中 localIdentName 是設置生成樣式的命名規則。
使用了 CSS Modules 後,就至關於給每一個 class 名外加了 :local,以此來實現樣式的局部化。若是咱們想切換到全局模式,可使用 :global 包裹
對於樣式複用,CSS Modules 只提供了惟一的方式來處理——composes 組合。
/* components/Button.css */ .base { /* 全部通用的樣式 */ }
/* settings.css */.primary-color { color: #f40; }
.primary {composes: base; composes: $primary-color from './settings.css'; /* primary 其餘樣式 */}
若是不想頻繁地輸入 styles.**,可使用 react-css-modules 庫。它經過高階組件的形式來 避免重複輸入 styles.**。能夠這麼寫---styleName="root"
使用 CSS Modules,容易使用 :global 去解決特殊狀況,使用 react-css-modules 可寫成 <div className="global-css" styleName="local-module"></div>,
這種形式輕鬆對應全局和局部;
跨級組件通訊:
一、
在子組件定義 static contextTypes = {color: PropTypes.string} ,經過this.context.color獲取頂層組件的color屬性
在頂層組件定義 static childContextTypes = {color: PropTypes.string}, 實現方法 getChildContext() {return{color: 'red'}
二、
沒有嵌套關係的,那隻能經過能夠影響全局的一些機制去考慮。import { EventEmitter } from 'events';
在 componentDidMount 事件中,若是組件掛載完成,再訂閱事件;當組件卸載的時候,在 componentWillUnmount 事件中取消事件的訂閱。
mixins
對於廣義的 mixin 方法,就是用賦值的方式將 mixin 對象裏的方法都掛載到原對象上,來實 現對對象的混入。
React 在使用 createClass 構建組件時提供了 mixin 屬性,mixins: ['xxx','xxx'],
在不一樣的 mixin 裏實現兩個名字同樣的普通方法,這會形成衝突。所以, 在 React 中是不容許出現重名普通方法的 mixin。
若是是 React 生命週期定義的方法,則會將各個模塊的生命週期方法疊加在一塊兒順序執行。
使用咱們推薦的 ES6 classes 形式構建組件時,它並不支持 mixin。
對於實現 mixin 方法來講,這就沒什麼不同了。但既然講到了語法糖,就來說講另外一個語 法糖 decorator,正巧能夠用來實現 class 上的 mixin。
core-decorators 庫爲開發者提供了一些實用的 decorator,其中實現了咱們正想要的 @mixin。 下面解讀一下其核心實現:
function handleClass(target, mixins) {
if (!mixins.length) { throw new SyntaxError(`@mixin() class ${target.name} requires at least one mixin as an argument`); }
for (let i = 0, l = mixins.length; i < l; i++) {
// 獲取 mixins 的 attributes 對象
const descs = getOwnPropertyDescriptors(mixins[i]);
// 批量定義 mixins 的 attributes 對象
for (const key in descs) {
if (!(key in target.prototype)){
defineProperty(target.prototype, key, descs[key]);
}
}
}
}
源代碼十分簡單,它將每個 mixin 對象的方法都疊加到 target 對象的原型上 以達到 mixin 的目的。這樣,就能夠用 @mixin 來作多個重用模塊的疊加了。
這裏用了getOwnPropertyDescriptor 和 defineProperty 這兩個方法,
好處在於 defineProperty 這個方法,也就是定義與賦值的區別,定義是 對已有的定義,賦值則是覆蓋已有的定義。因此說前者並不會覆蓋已有方法,但後者會。
HOC:
屬性代理是常見高階組件的實現方法
const MyContainer = (WrappedComponent) =>
class extends Component {---這裏將Component替換成WrappedComponent就實現了反向繼承,除了一些靜態方法,包括生命週期,state,各類function,咱們均可以獲得。
咱們同時能夠以此進行hijack(劫持),也就是控制它的render函數。在render()中調用superRender(),而後經過在外層嵌套的方式改變原有渲染
handleClick = () => {console.log('clicked');}
render() {
const otherProps = {handleClick:this.handleClick}
return <WrappedComponent {...this.props} ref={instanceComponent => this.instanceComponent = instanceComponent}/>;
}
}
export default MyContainer//--->這是一個hoc組件
class MyComponent extends Component { ... }
export default MyContainer(MyComponent);
高階組件能夠看作是裝飾器模式(Decorator Pattern)在React的實現。即容許向一個現有的對象添加新的功能,同時又不改變其結構,屬於包裝模式(Wrapper Pattern)的一種
ES7中添加了一個decorator的屬性,使用@符表示,上面一行能夠改寫成@MyContainer
能夠在hoc高階組件中自定義事件,並經過props傳遞下去,在hoc高階組件中使用ref,獲取當前被包含組件的引用ref
react生命週期:
主要經過 3 個階段進行管理—— MOUNTING、RECEIVE_PROPS 和 UNMOUNTING。
當首次掛載組件時,按順序執行 getDefaultProps、getInitialState、componentWillMount、render 和 componentDidMount。
當從新掛載組件時,此時按順序執行 getInitialState、componentWillMount、render 和componentDidMount,但並不執行 getDefaultProps。
當再次渲染組件時,組件接受到更新狀態,此時按順序執行 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate。
當卸載組件時,執行 componentWillUnmount。
creatClass是建立自定義組件的入口方法,負責管理生命週期中的 getDefaultProps。該方法在整個生命週期中只執行一次,這樣全部實例初始化的 props 將會被共享。
因爲 getDefaultProps 是經過構造函數進行管理的,因此也是整個生命週期中最早開始執行 的。
在 componentWillMount 中調用 setState 方法,是不會觸發 re-render的,而是會進行 state 合併,且 inst.state = this._processPendingState (inst.props, inst.context) 是在 componentWillMount 以後執行的,
所以 componentWillMount 中 的 this.state 並非最新的,在 render 中才能夠獲取更新後的 this.state。
React 是利用更新隊列 this._pendingStateQueue 以及更新狀態 this._pendingReplace State 和 this._pendingForceUpdate 來實現 setState 的異步更新機制。
在 componentWillReceiveProps 中調 用 setState,是不會觸發 re-render 的,而是會進行 state 合併。
且在 componentWillReceiveProps、shouldComponentUpdate 和 componentWillUpdate 中也仍是沒法獲取到更新後的 this.state,
即此 時訪問的 this.state 仍然是未更新的數據,須要設置 inst.state = nextState 後才能夠,所以 只有在 render 和 componentDidUpdate 中才能獲取到更新後的 this.state。
在 componentWillUnmount,則執行並重置全部相關參數、更新隊列以及更新狀態,如 果此時在 componentWillUnmount 中調用 setState,
是不會觸發 re-render 的,這是由於全部更新 隊列和更新狀態都被重置爲 null,並清除了公共類,完成了組件卸載操做。
componentWillMount、componentWillReceiveProps、componentWillUpdate
被getDerivedStateFromProps 取代了。主要因爲fiber致使上面三個函數會重複調用
setstate:
React 利用狀態隊列機制實現了 setState的異步更新,避免頻繁地重複更新 state。
1.將setState傳入的partialState參數存儲在當前組件實例的state暫存隊列中。
當調用 setState 時,實際上會執行 enqueueSetState 方法,_pendingStateQueue 更新隊列 對partialState 進行合併操做,
經過 enqueueUpdate 執行 state 更新。
function enqueueUpdate(component) {
ensureInjected();
// 若是不處於批量更新模式
if (!batchingStrategy.isBatchingUpdates) {
3.若是未處於批量更新狀態,將批量更新狀態標識設置爲true,用事務再次調用前一步方法,保證當前組件加入到了待更新組件隊列中。
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
2.判斷當前React是否處於批量更新狀態,若是是,將當前組件加入待更新的組件隊列中。
// 若是處於批量更新模式,則將該組件保存在 dirtyComponents 中
dirtyComponents.push(component);
}
batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
if (alreadyBatchingUpdates) {
callback(a, b, c, d, e);
} else {
4.調用事務的waper方法,遍歷待更新組件隊列依次執行更新。
transaction.perform(callback, null, a, b, c, d, e);----事務
}
},
5.執行生命週期componentWillReceiveProps。
6.將組件的state暫存隊列中的state進行合併,得到最終要更新的state對象,並將隊列置爲空。
7.執行生命週期componentShouldUpdate,根據返回值判斷是否要繼續更新。
8.執行生命週期componentWillUpdate。
9.執行真正的更新,render。
10.執行生命週期componentDidUpdate。
在 shouldComponentUpdate 或 componentWillUpdate 方 法 中 調 用 setState ,又會調用
shouldComponentUpdate 和 componentWillUpdate 方法,所以形成循環調用,使得瀏覽器內存佔滿後崩潰。
重構history類。並做爲react.context的value傳遞給route子組件
在url中多了以#結尾的hash值,可是賦值先後雖然頁面的hash值改變致使頁面完整的url發生了改變,可是頁面是不會刷新的。
此外,還有一個名爲hashchange的事件,能夠監聽hash的變化,咱們能夠經過下面兩種方式來監聽hash的變化:
window.onhashchange=function(event){
console.log(event);
}
History:
History.back(): 返回瀏覽器會話歷史中的上一頁,跟瀏覽器的回退按鈕功能相同
History.go(): 能夠跳轉到瀏覽器會話歷史中的指定的某一個記錄頁
History.forward():指向瀏覽器會話歷史中的下一頁,跟瀏覽器的前進按鈕相同
History.pushState():pushState能夠將給定的數據壓入到瀏覽器會話歷史棧中,該方法接收3個參數,對象,title和一串url。pushState後會改變當前頁面url,可是不會伴隨着刷新
History.replaceState():replaceState將當前的會話頁面的url替換成指定的數據,replaceState後也會改變當前頁面的url,可是也不會刷新頁面。
BrowserRouter:
用<BrowserRouter> 組件包裹整個App系統後,就是經過html5的history來實現無刷新條件下的前端路由。
若是用history作爲路由的基礎,那麼須要用到的是history.pushState和history.replaceState,在不刷新的狀況下能夠改變url的地址,
且若是頁面發生回退back或者forward時,會觸發popstate事件。
監聽history.urlChange(),更新context中的location和history的值,以及match屬性
Link:
<Link>相似於html中的a標籤,此外<Link>在改變url的時候,能夠將一些屬性傳遞給匹配成功的Route,供相應的組件渲染的時候使用。
to屬性的值也能夠是一個對象,該對象能夠包含一下幾個屬性:pathname、seacth、hash和state,其中前3個參數與如何改變url有關,
最後一個state參數是給相應的改變url時,傳遞一個對象參數。
舉例來講: <Link to={{pathname:'/home',search:'?sort=name',hash:'#edit',state:{a:1}}}>Home</Link>
class Link extends React.Component {
handleClick = event => {
event.preventDefault();
const { history } = this.context.router;
const { replace, to } = this.props;
if (replace) {
history.replace(replace);
} else {
history.push(to);
}
}
};
render(){
const { replace, to, innerRef, ...props } = this.props;
<a {...props} onClick={this.handleClick}/>
}
}
Route
Route組件也很簡單,其props中接受一個最主要的屬性path,Route作的事情只有一件:
當url改變的時候,根據context中history的變化,將history.location.pathname屬性與自身的path作對比,若是匹配成功,則渲染該組件的componet或者children屬性所賦值的那個組件。
跨域請求的一種解決方案:
const proxy = require('http-proxy-middleware');
module.exports = function(app) {
app.use(proxy('/api', { target: 'http://localhost:5000/' }));
};
suspend和lazy懶加載
react 性能優化:
網絡層的優化,gzip等
pureComponent
在shouldUpdate()深淺比較(immutable.js可避免淺比較帶來的問題)
key
React.Memo來緩存組件
避免動態建立函數props
suspend和lazy懶加載
使用css隱藏和顯示組件而不是使用?:來加載和卸載組件
避免深層次的state數據傳遞,會讓無關中間組件render,可經過context傳數據
組件顆粒化,減小無關的render
reselector: 接受Redux store state做爲參數緩存mapStateToProps的計算結果,避免重複計算
immutablejs 提高操做數據結構的性能