React組件設想組件分類展現組件和容器組件展現組件容器組件關注事物的展現關注事物怎樣事變可以包含展現和容器組件,而且平常會有DOM標籤和css款式可以包含展現和容器組件,而且不會
React組件設想
組件分類 展現組件和容器組件 展現組件 容器組件 關注事物的展現 關注事物怎樣事變 可以包含展現和容器組件,而且平常會有DOM
標籤和css
款式 可以包含展現和容器組件,而且不會有DOM
標籤和css
款式 常常許可經由歷程this.props.children
通報 供應數據和行動給容器組件或許展現組件 對第三方沒有任何依靠,比方store
或許 flux action
挪用flux action
而且供應他們的回調給展現組件 不要指定數據怎樣加載和變化 作為數據源,一般採納較高階的組件,而不是自身寫,比方React Redux
的connect()
, Relay的createContainer()
, Flux Utils
的Container.create()
僅經由歷程屬性獵取數據和回調 null
很少有自身的狀況,縱然有,也是自身的UI
狀況 null
除非他們須要的自身的狀況,性命周期,或機能優化才會被寫為功用組件 null
下面是一個可以會常常寫的組件,批評列表組件,數據交互和展現都放到了一個組件內里。
// CommentList.js class CommentList extends React.Component { constructor() { super(); this.state = { comments: [] } } componentDidMount() { $.ajax({ url: "/my-comments.json", dataType: 'json', success: function(comments) { this.setState({comments: comments}); }.bind(this) }); } render() { return {this.state.comments.map(renderComment)} ; } renderComment({body, author}) { return {body}—{author} ; } }
我們對上面的組件舉行拆分,把他拆分紅容器組件 CommentListContainer.js
和展現組件 CommentList
。
// CommentListContainer.js class CommentListContainer extends React.Component { constructor() { super(); this.state = { comments: [] } } componentDidMount() { $.ajax({ url: "/my-comments.json", dataType: 'json', success: function(comments) { this.setState({comments: comments}); }.bind(this) }); } render() { return ; } } // CommentList.js class CommentList extends React.Component { constructor(props) { super(props); } render() { return {this.props.comments.map(renderComment)} ; } renderComment({body, author}) { return {body}—{author} ; } }
上風:
展現和容器更好的星散,更好的邃曉運用程序和UI
重用性高,展現組件可以用於多個差別的state
數據源 展現組件就是你的調色板,可以把他們放到零丁的頁面,在不影響運用程序的狀況下,讓設想師調解UI
迫使你星散標籤,到達更高的可用性 有狀況組件和無狀況組件 下面是一個最簡樸的無狀況組件的例子:
function HelloComponent(props, /* context */) { return Hello {props.name}
} ReactDOM.render( , mountNode)
可以看到,底本須要寫“類”定義(React.createClass
或許 class YourComponent extends React.Component
)來建立自身組件的定義(有狀況組件),現在被精簡成了只寫一個 render
函數。更值得一提的是,由於僅僅是一個無狀況函數,React
在襯着的時刻也省掉了將“組件類” 實例化的歷程。
連繫 ES6
的解構賦值,可以讓代碼更精簡。比方下面這個 Input
組件:
function Input({ label, name, value, ...props }, { defaultTheme }) { const { theme, autoFocus, ...rootProps } = props return ( htmlFor={name} children={label || defaultLabel} {...rootProps} > name={name} type="text" value={value || ''} theme={theme || defaultTheme} {...props} /> )} Input.cOntextTypes= {defaultTheme: React.PropTypes.object};
無狀況組件不像上述兩種要領在挪用時會建立新實例,它建立時始終堅持了一個實例,防止了不必要的搜檢和內存分派,做到了內部優化。
無狀況組件不支撐 “ref”
高階組件 高階組件經由歷程函數和閉包,轉變已有組件的行動,本質上就是 Decorator
形式在 React
的一種完成。
當寫着寫着無狀況組件的時刻,有一天遽然發明須要狀況處置懲罰了,那末無需完整返工:) 每每我們須要狀況的時刻,這個需求是可以重用的。
高階組件加無狀況組件,則大大增強了全部代碼的可測試性和可維護性。同時不停“誘使”我們寫出組合性更好的代碼。
高階函數 function welcome() { let username = localStorage.getItem('username'); console.log('welcome ' + username); } function goodbey() { let username = localStorage.getItem('username'); console.log('goodbey ' + username); } welcome(); goodbey();
我們發明兩個函數有一句代碼是一樣的,這叫冗餘唉。(日常平凡可以會有一大段代碼的冗餘)。
下面我們要寫一个中心函數,讀取username,他來擔任把username通報給兩個函數。
function welcome(username) { console.log('welcome ' + username); } function goodbey(username) { console.log('goodbey ' + username); } function wrapWithUsername(wrappedFunc) { let newFunc = () => { let username = localStorage.getItem('username'); wrappedFunc(username); }; return newFunc; } welcome = wrapWithUsername(welcome); goodbey = wrapWithUsername(goodbey); welcome(); goodbey();
好了,我們內里的 wrapWithUsername
函數就是一個“高階函數”。 他做了什麼?他幫我們處置懲罰了 username
,通報給目的函數。我們挪用終究的函數 welcome
的時刻,基本不必體貼 username
是怎樣來的。
聞一知十的高階組件 下面是兩個冗餘的組件。
import React, {Component} from 'react' class Welcome extends Component { constructor(props) { super(props); this.state = { username: '' } } componentWillMount() { let username = localStorage.getItem('username'); this.setState({ username: username }) } render() { return ( welcome {this.state.username}
) } } export default Welcome;
import React, {Component} from 'react' class Goodbye extends Component { constructor(props) { super(props); this.state = { username: '' } } componentWillMount() { let username = localStorage.getItem('username'); this.setState({ username: username }) } render() { return ( goodbye {this.state.username}
) } } export default Goodbye;
我們可以經由歷程方才高階函數的思想來建立一个中心組件,也就是我們說的高階組件。
import React, {Component} from 'react' export default (WrappedComponent) => { class NewComponent extends Component { constructor() { super(); this.state = { username: '' } } componentWillMount() { let username = localStorage.getItem('username'); this.setState({ username: username }) } render() { return } } return NewComponent }
import React, {Component} from 'react'; import wrapWithUsername from 'wrapWithUsername'; class Welcome extends Component { render() { return ( welcome {this.props.username}
) } } Welcome = wrapWithUsername(Welcome); export default Welcome;
import React, {Component} from 'react'; import wrapWithUsername from 'wrapWithUsername'; class Goodbye extends Component { render() { return ( goodbye {this.props.username}
) } } Goodbye = wrapWithUsername(Goodbye); export default Goodbye;
看到沒有,高階組件就是把 username
經由歷程 props
通報給目的組件了。目的組件只管從 props
內里拿來用就好了。
為了代碼的復用性,我們應當只管削減代碼的冗餘。
提取同享的state,假如有兩個組件都須要加載一樣的數據,那末他們會有雷同的 componentDidMount 函數。 找出反覆的代碼,每一個組件中constructor 和 componentDidMount都乾著一樣的事變,別的,在數據拉取時都邑顯現Loading… 案牘,那末我們應當思索怎樣運用高階組件來提取這些要領。 遷徙反覆的代碼到高階組件 包裹組件,而且運用props替代state 只管地簡化 組件開闢基本思想 單功用準繩 運用react時,組件或容器的代碼在基本上必需只擔任一塊UI功用。
讓組件堅持簡樸 假如組件基本不須要狀況,那末就運用函數定義的無狀況組件。 從機能上來講,函數定義的無狀況組件 > ES6 class
定義的組件 > 經由歷程 React.createClass()
定義的組件。 僅通報組件所須要的屬性。只要當屬性列表太長時,才運用{...this.props}
舉行通報。 假如組件內里有太多的推斷邏輯(if-else
語句)一般意味着這個組件須要被拆分紅更細的組件或模塊。 運用邃曉的定名可以讓開闢者邃曉它的功用,有助於組件復用。 基本準則 在shouldComponentUpdate
中防止不必要的搜檢. 只管運用不可變數據範例(Immutable
). 編寫針對產物環境的打包設置(Production Build
). 經由歷程Chrome Timeline
來紀錄組件所消耗的資本. 在componentWillMount
或許componentDidMount
內里經由歷程setTimeOut
或許requestAnimationFram
來耽誤實行那些須要大批盤算的使命. 組件開闢技能 form表單里的受控組件和不受控組件 受控組件 在大多數狀況下,我們引薦運用受控組件來完成表單。在受控組件中,表單數據由 React 組件擔任處置懲罰。下面是一個典範的受控組建。
class NameForm extends React.Component { constructor(props) { super(props); this.state = {value: ''}; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event) { this.setState({value: event.target.value}); } handleSubmit(event) { alert('A name was submitted: ' + this.state.value); event.preventDefault(); } render() { return ( ); } }
設置表單元素的value
屬性以後,其顯現值將由this.state.value
決議,以滿足React
狀況的統一數據理念。每次鍵盤敲擊以後會實行handleChange
要領以更新React
狀況,顯現值也將跟着用戶的輸入轉變。
關於受控組件來講,每一次 state
(狀況)變化都邑伴有相干聯的處置懲罰函數。這使得可以直接修正或考證用戶的輸入和提交表單。
不受控組件 由於不受控組件的數據泉源是 DOM 元素,當運用不受控組件時很輕易完成 React 代碼與非 React 代碼的集成。假如你願望的是疾速開闢、不請求代碼質量,不受控組件可以肯定程度上削減代碼量。不然。你應當運用受控組件。
平常狀況下不受控組件我們運用ref
來獵取DOM
元素舉行操縱。
class NameForm extends React.Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind(this); } handleSubmit(event) { alert('A name was submitted: ' + this.input.value); event.preventDefault(); } render() { return ( Name: this.input = input} /> ); } }
組件前提推斷 三元函數組件推斷襯着 const sampleCompOnent= () => { return isTrue ? True!
: false!
};
運用&&表達式替代不必要的三元函數 const sampleCompOnent= () => { return isTrue ? True!
: };
const sampleCompOnent= () => { return isTrue && True!
};
須要注重的是假如isTrue
為 0 ,實在會轉換成 false
,然則在頁面中顯現的時刻,&&
照樣會返回0
顯現到頁面中。
多重嵌套推斷 // 題目代碼 const sampleCompOnent= () => { return ( {flag && flag2 && !flag3
? flag4
?
Blah
: flag5
?
Meh
:
Herp
:
Derp
}
) };
處理計劃:
最好計劃: 將邏輯移到子組件內部 運用IIFE(Immediately-Invoked Function Expression 馬上實行函數) 滿足前提的時刻運用return強迫跳出函數 const sampleCompOnent= () => { const basicCOndition= flag && flag2 && !flag3; if (!basicCondition) return Derp
; if (flag4) return Blah
; if (flag5) return Meh
; return Herp
}
setState異步性 在某些狀況下,React
框架出於機能優化斟酌,可以會將屢次state
更新兼并成一次更新。正由於如此,setState
實際上是一個異步的函數。 假如在挪用setState()
函數以後嘗試去接見this.state
,你獲得的可以照樣setState()
函數實行之前的效果。
然則,有一些行動也會阻撓React
框架自身關於屢次state
更新的兼并,從而讓state
的更新變得同步化。 比方: eventListeners
, Ajax
, setTimeout
等等。
React
框架之所以在挑選在挪用setState
函數以後馬上更新state而不是採納框架默許的體式格局,即兼并屢次state
更新為一次更新,是由於這些函數挪用(fetch
,setTimeout
等瀏覽器層面的API
挪用)並不處於React
框架的高低文中,React
沒有方法對其舉行掌握。React
在此時採納的戰略就是實時更新,確保在這些函數實行以後的其他代碼能拿到準確的數據(即更新過的state
)。
處理setState函數異步的方法? 依據React
官方文檔,setState
函數實際上吸收兩個參數,个中第二個參數範例是一個函數,作為setState
函數實行后的回調。經由歷程傳入回調函數的體式格局,React
可以保證傳入的回調函數肯定是在setState
勝利更新this.state
以後再實行。
this.setState({count: 1}, () => { console.log(this.state.count); // 1 })
React源碼中setState的完成 ReactComponent.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); if (callback) { this.updater.enqueueCallback(this, callback, 'setState'); } };
updater
的這兩個要領,和React
底層的Virtual Dom
(假造DOM樹)的diff
算法有嚴密的關聯,所以真正決議同步照樣異步的實際上是Virtual DOM
的diff
算法。
依靠注入 在React
中,想做依靠注入(Dependency Injection
)實在相稱簡樸。可以經由歷程props
來舉行通報。然則,假如組件數目許多,而且組件嵌套條理很深的話,這類體式格局就不太適宜。
高階組件 // inject.jsx var title = 'React Dependency Injection'; export default function inject(Component) { return class Injector extends React.Component { render() { return ( {...this.state} {...this.props} title={ title } /> ) } }; }
// Title.jsx export default function Title(props) { return { props.title } ; }
// Header.jsx import inject from './inject.jsx'; import Title from './Title.jsx'; var EnhancedTitle = inject(Title); export default function Header() { return ( ); }
context React v16.3.0
之前的 Context
:
var cOntext= { title: 'React in patterns' }; class App extends React.Component { getChildContext() { return context; } // ... } App.childCOntextTypes= { title: PropTypes.string };
class Inject extends React.Component { render() { var title = this.context.title; // ... } } Inject.cOntextTypes= { title: PropTypes.string };
之前的 Context
作為一個試驗性子的 API
,直到 React v16.3.0
版本前都一向不被官方所首倡去運用,其主要緣由就是由於在子組件中運用 Context
會損壞 React
運用的分型架構。
這裏的分形架構指的是從抱負的 React
運用的根組件樹中抽取的恣意一部分都還是一個可以直接運轉的子組件樹。在這個子組件樹之上再包一層,就可以將它無縫地移植到恣意一個其他的根組件樹中。
但假如根組件樹中有恣意一個組件運用了支撐透傳的 Context
API
,那末假如把包含了這個組件的子組件樹零丁拿出來,由於缺少了供應 Context
值的根組件樹,這時候的這個子組件樹是沒法直接運轉的。
而且他有一個致命缺點:任何一个中心通報的組件shouldComponentUpdate
函數返回false
,組件都不會獲得更新。
新的Context Api
新的Context Api
採納聲明式的寫法,而且可以透過shouldComponentUpdate
函數返回false
的組件繼承向下流傳,以保證目的組件肯定可以吸收到頂層組件 Context
值的更新,一舉處理了現有 Context API
的兩大弊病,也終究成為了 React
中的第一級(first-class) API
。
新的 Context API 分為三個構成部分:
React.createContext
用於初始化一個 Context
。 XXXContext.Provider
作為頂層組件吸收一個名為 value
的 prop
,可以吸收恣意須要被放入 Context
中的字符串,数字,以至是函數。 XXXContext.Consumer
作為目的組件可以出現在組件樹的恣意位置(在 Provider
以後),吸收 children prop
,這裏的 children
必需是一個函數(cOntext=> ()
)用來吸收從頂層傳來的 Context
。 const ThemeCOntext= React.createContext('light'); class App extends React.Component { render() { return ( ); } } function Toolbar(props) { return (
); } function ThemedButton(props) { return ( {theme => } ); }
事宜處置懲罰中的this指向題目 class Switcher extends React.Component { constructor(props) { super(props); this.state = { name: 'React in patterns' }; } render() { return ( click me ); } _handleButtonClick() { console.log(`Button is clicked inside ${ this.state.name }`); // 將致使 // Uncaught TypeError: Cannot read property 'state' of null } }
我們可以經由歷程下面三種體式格局簡樸完成this指向的綁定:
在constructor
中事前綁定 this._buttOnClick= this._handleButtonClick.bind(this);
挪用時運用箭頭函數 this._buttonClick() }>
ES7中的綁定操縱符
給setState傳入回調函數 setState() 不僅能接收一個對象,還能接收一個函數作為參數呢,該函數接收該組件前一刻的 state 以及當前的 props 作為參數,盤算和返回下一刻的 state。
// assuming this.state.count === 0 this.setState({count: this.state.count + 1}); this.setState({count: this.state.count + 1}); this.setState({count: this.state.count + 1}); // this.state.count === 1, not 3 this.setState((prevState, props) => ({ count: prevState.count + props.increment }));
// Passing object this.setState({ expanded: !this.state.expanded }); // Passing function this.setState(prevState => ({ expanded: !prevState.expanded }));
組件切換技能 import HomePage from './HomePage.jsx'; import AboutPage from './AboutPage.jsx'; import UserPage from './UserPage.jsx'; import FourOhFourPage from './FourOhFourPage.jsx'; const PAGES = { home: HomePage, about: AboutPage, user: UserPage }; const Page = (props) => { const Handler = PAGES[props.page] || FourOhFourPage; return };
React style 組件分類 基本組件, 規劃組件, 排版組件
給無狀況的純UI組件運用款式 請堅持款式闊別那些離不開state的組件. 比方路由, 視圖, 容器, 表單, 規劃等等不該當有任何的款式或許css class出現在組件上. 相反, 這些龐雜的營業組件應當有一些帶有基本功用的無狀況UI組件構成.
class SampleComponent extends Component { render() { return ( name='username' value={username} OnChange={this.handleChange}/> type='password' name='password' value={password} OnChange={this.handleChange}/> type='submit' children='Sign In'/> ) } } // 表達組件(帶款式) const Button = ({ ...props }) => { const sx = { fontFamily: 'inherit', fontSize: 'inherit', fontWeight: 'bold', textDecoration: 'none', display: 'inline-block', margin: 0, paddingTop: 8, paddingBottom: 8, paddingLeft: 16, paddingRight: 16, border: 0, color: 'white', backgroundColor: 'blue', WebkitAppearance: 'none', MozAppearance: 'none' } return ( export const black = '#111'; export const blue = '#07c'; export const colors = { white, black, blue }; export const space = [ 0, 8, 16, 32, 64 ]; const styles = { bold: 600, space, colors }; export default styles
// button.jsx import React from 'react' import { bold, space, colors } from './styles' const Button = ({ ...props }) => { const sx = { fontFamily: 'inherit', fontSize: 'inherit', fontWeight: bold, textDecoration: 'none', display: 'inline-block', margin: 0, paddingTop: space[1], paddingBottom: space[1], paddingLeft: space[2], paddingRight: space[2], border: 0, color: colors.white, backgroundColor: colors.blue, WebkitAppearance: 'none', MozAppearance: 'none' }; return ( ? {[prop]: scale[x]} : null }; const getScaledProperty = createScaledPropertyGetter(scale); export const getMargin = getScaledProperty('margin'); export const getPadding = getScaledProperty('padding'); // 款式函數的用法 const Box = ({ m, p, ...props }) => { const sx = { ...getMargin(m), ...getPadding(p) }; return
)}
在上面的兩個壞實踐中, 每次我們都邑去建立一個新的函數實體. 和第一個例子相似, 新的函數實體味讓我們的淺比較返回false
, 致使組件被從新襯着. 所以我們須要在更早的時刻去bind
我們的函數.