React組件設(shè)計(jì)
組件分類
展示組件和容器組件
展示組件 容器組件
關(guān)注事物的展示 關(guān)注事物如何工作
可能包含展示和容器組件ID嵌套樣式問題,并且一般會(huì)有DOM標(biāo)簽和css樣式 可能包含展示和容器組件,并且不會(huì)有DOM標(biāo)簽和css樣式
常常允許通過(guò)this.props.children傳遞 提供數(shù)據(jù)和行為給容器組件或者展示組件
對(duì)第三方?jīng)]有任何依賴ID嵌套樣式問題,比如store 或者 flux action 調(diào)用flux action 并且提供他們的回調(diào)給展示組件
不要指定數(shù)據(jù)如何加載和變化 作為數(shù)據(jù)源ID嵌套樣式問題,通常采用較高階的組件,而不是自己寫,比如React Redux的connect(), Relay的createContainer(), Flux Utils的Container.create()
僅通過(guò)屬性獲取數(shù)據(jù)和回調(diào)
很少有自己的狀態(tài)ID嵌套樣式問題,即使有,也是自己的UI狀態(tài)
除非他們需要的自己的狀態(tài)ID嵌套樣式問題,生命周期,或性能優(yōu)化才會(huì)被寫為功能組件
下面是一個(gè)可能會(huì)經(jīng)常寫的組件,評(píng)論列表組件,數(shù)據(jù)交互和展示都放到了一個(gè)組件里面ID嵌套樣式問題。
// 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() {
021yin.com ments.map(renderComment)} /ul;
renderComment({body, author}) {
return li{body}—{author}/li;
我們對(duì)上面的組件進(jìn)行拆分,把他拆分成容器組件 CommentListContainer.js 和展示組件 CommentListID嵌套樣式問題。
// 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() {
021yin.com ments} /;
// CommentList.js
class CommentList extends React.Component {
constructor(props) {
super(props);
render() {
021yin.com ments.map(renderComment)} /ul;
renderComment({body, author}) {
return li{body}—{author}/li;
優(yōu)勢(shì):
展示和容器更好的分離ID嵌套樣式問題,更好的理解應(yīng)用程序和UI
重用性高ID嵌套樣式問題,展示組件可以用于多個(gè)不同的state數(shù)據(jù)源
展示組件就是你的調(diào)色板ID嵌套樣式問題,可以把他們放到單獨(dú)的頁(yè)面,在不影響應(yīng)用程序的情況下,讓設(shè)計(jì)師調(diào)整UI
迫使你分離標(biāo)簽ID嵌套樣式問題,達(dá)到更高的可用性
有狀態(tài)組件和無(wú)狀態(tài)組件
下面是一個(gè)最簡(jiǎn)單的無(wú)狀態(tài)組件的例子:
function HelloComponent(props, /* context */) {
return divHello {props.name}/div
ReactDOM.render(HelloComponent name="Sebastian" /, mountNode)
可以看到,原本需要寫“類”定義(React.createClass 或者 class YourComponent extends React.Component)來(lái)創(chuàng)建自己組件的定義(有狀態(tài)組件),現(xiàn)在被精簡(jiǎn)成了只寫一個(gè) render 函數(shù)ID嵌套樣式問題。更值得一提的是,由于僅僅是一個(gè)無(wú)狀態(tài)函數(shù),React 在渲染的時(shí)候也省掉了將“組件類” 實(shí)例化的過(guò)程。
結(jié)合 ES6 的解構(gòu)賦值,可以讓代碼更精簡(jiǎn)ID嵌套樣式問題。例如下面這個(gè) Input 組件:
function Input({ label, name, value, ...props }, { defaultTheme }) {
const { theme, autoFocus, ...rootProps } = props
return (
label
htmlFor={name}
children={label || defaultLabel}
{...rootProps}
input
name={name}
type="text"
value={value || ''}
theme={theme || defaultTheme}
{...props}
Input.contextTypes = {defaultTheme: React.PropTypes.object};
無(wú)狀態(tài)組件不像上述兩種方法在調(diào)用時(shí)會(huì)創(chuàng)建新實(shí)例,它創(chuàng)建時(shí)始終保持了一個(gè)實(shí)例,避免了不必要的檢查和內(nèi)存分配,做到了內(nèi)部?jī)?yōu)化ID嵌套樣式問題。
無(wú)狀態(tài)組件不支持 "ref"
高階組件
高階組件通過(guò)函數(shù)和閉包,改變已有組件的行為,本質(zhì)上就是 Decorator 模式在 React 的一種實(shí)現(xiàn)ID嵌套樣式問題。
當(dāng)寫著寫著無(wú)狀態(tài)組件的時(shí)候ID嵌套樣式問題,有一天忽然發(fā)現(xiàn)需要狀態(tài)處理了,那么無(wú)需徹底返工:)
往往我們需要狀態(tài)的時(shí)候,這個(gè)需求是可以重用的ID嵌套樣式問題。
高階組件加無(wú)狀態(tài)組件,則大大增強(qiáng)了整個(gè)代碼的可測(cè)試性和可維護(hù)性ID嵌套樣式問題。同時(shí)不斷“誘使”我們寫出組合性更好的代碼。
高階函數(shù)
function welcome() {
let username = localStorage.getItem('username');
console.log('welcome ' + username);
function goodbey() {
let username = localStorage.getItem('username');
console.log('goodbey ' + username);
welcome();
goodbey();
我們發(fā)現(xiàn)兩個(gè)函數(shù)有一句代碼是一樣的,這叫冗余唉ID嵌套樣式問題。(平時(shí)可能會(huì)有一大段代碼的冗余)。
下面我們要寫一個(gè)中間函數(shù),讀取username,他來(lái)負(fù)責(zé)把username傳遞給兩個(gè)函數(shù)ID嵌套樣式問題。
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 函數(shù)就是一個(gè)“高階函數(shù)”ID嵌套樣式問題。
他做了什么?他幫我們處理了 username,傳遞給目標(biāo)函數(shù)ID嵌套樣式問題。我們調(diào)用最終的函數(shù) welcome的時(shí)候,根本不用關(guān)心 username是怎么來(lái)的。
舉一反三的高階組件
下面是兩個(gè)冗余的組件ID嵌套樣式問題。
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 (
divwelcome {this.state.username}/div
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 (
divgoodbye {this.state.username}/div
export default Goodbye;
我們可以通過(guò)剛剛高階函數(shù)的思想來(lái)創(chuàng)建一個(gè)中間組件,也就是我們說(shuō)的高階組件ID嵌套樣式問題。
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 WrappedComponent username={this.state.username}/
return NewComponent
import React, {Component} from 'react';
import wrapWithUsername from 'wrapWithUsername';
class Welcome extends Component {
render() {
return (
divwelcome {this.props.username}/div
Welcome = wrapWithUsername(Welcome);
export default Welcome;
import React, {Component} from 'react';
import wrapWithUsername from 'wrapWithUsername';
class Goodbye extends Component {
render() {
return (
divgoodbye {this.props.username}/div
Goodbye = wrapWithUsername(Goodbye);
export default Goodbye;
看到?jīng)]有,高階組件就是把 username 通過(guò) props 傳遞給目標(biāo)組件了ID嵌套樣式問題。目標(biāo)組件只管從 props里面拿來(lái)用就好了。
為了代碼的復(fù)用性,我們應(yīng)該盡量減少代碼的冗余ID嵌套樣式問題。
提取共享的state,如果有兩個(gè)組件都需要加載同樣的數(shù)據(jù),那么他們會(huì)有相同的 componentDidMount 函數(shù)ID嵌套樣式問題。
找出重復(fù)的代碼,每個(gè)組件中constructor 和 componentDidMount都干著同樣的事情,另外,在數(shù)據(jù)拉取時(shí)都會(huì)顯示Loading... 文案,那么我們應(yīng)該思考如何使用高階組件來(lái)提取這些方法ID嵌套樣式問題。
遷移重復(fù)的代碼到高階組件
包裹組件ID嵌套樣式問題,并且使用props替換state
盡可能地簡(jiǎn)化
組件開發(fā)基本思想
單功能原則
使用react時(shí),組件或容器的代碼在根本上必須只負(fù)責(zé)一塊UI功能ID嵌套樣式問題。
讓組件保持簡(jiǎn)單
如果組件根本不需要狀態(tài),那么就使用函數(shù)定義的無(wú)狀態(tài)組件ID嵌套樣式問題。
從性能上來(lái)說(shuō),函數(shù)定義的無(wú)狀態(tài)組件 ES6 class 定義的組件 通過(guò) React.createClass() 定義的組件ID嵌套樣式問題。
僅傳遞組件所需要的屬性ID嵌套樣式問題。只有當(dāng)屬性列表太長(zhǎng)時(shí),才使用{...this.props}進(jìn)行傳遞。
如果組件里面有太多的判斷邏輯(if-else語(yǔ)句)通常意味著這個(gè)組件需要被拆分成更細(xì)的組件或模塊ID嵌套樣式問題。
使用明確的命名能夠讓開發(fā)者明白它的功能,有助于組件復(fù)用ID嵌套樣式問題。
基本準(zhǔn)則
在shouldComponentUpdate中避免不必要的檢查.
盡量使用不可變數(shù)據(jù)類型(Immutable).
編寫針對(duì)產(chǎn)品環(huán)境的打包配置(Production Build).
通過(guò)Chrome Timeline來(lái)記錄組件所耗費(fèi)的資源.
在componentWillMount或者componentDidMount里面通過(guò)setTimeOut或者requestAnimationFram來(lái)延遲執(zhí)行那些需要大量計(jì)算的任務(wù).
組件開發(fā)技巧
form表單里的受控組件和不受控組件
受控組件
在大多數(shù)情況下,我們推薦使用受控組件來(lái)實(shí)現(xiàn)表單ID嵌套樣式問題。在受控組件中,表單數(shù)據(jù)由 React 組件負(fù)責(zé)處理。下面是一個(gè)典型的受控組建。
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 (
form onSubmit={this.handleSubmit}
label
input type="text" value={this.state.value} onChange={this.handleChange} /
/label
input type="submit" value="Submit" /
/form
設(shè)置表單元素的value屬性之后,其顯示值將由this.state.value決定,以滿足React狀態(tài)的同一數(shù)據(jù)理念I(lǐng)D嵌套樣式問題。每次鍵盤敲擊之后會(huì)執(zhí)行handleChange方法以更新React狀態(tài),顯示值也將隨著用戶的輸入改變。
對(duì)于受控組件來(lái)說(shuō),每一次 state(狀態(tài))變化都會(huì)伴有相關(guān)聯(lián)的處理函數(shù)ID嵌套樣式問題。這使得可以直接修改或驗(yàn)證用戶的輸入和提交表單。
不受控組件
因?yàn)椴皇芸亟M件的數(shù)據(jù)來(lái)源是 DOM 元素,當(dāng)使用不受控組件時(shí)很容易實(shí)現(xiàn) React 代碼與非 React 代碼的集成ID嵌套樣式問題。如果你希望的是快速開發(fā)、不要求代碼質(zhì)量,不受控組件可以一定程度上減少代碼量。否則。你應(yīng)該使用受控組件。
一般情況下不受控組件我們使用ref來(lái)獲取DOM元素進(jìn)行操作ID嵌套樣式問題。
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 (
form onSubmit={this.handleSubmit}
label
Name:
input type="text" ref={(input) = this.input = input} /
/label
input type="submit" value="Submit" /
/form
組件條件判斷
三元函數(shù)組件判斷渲染
const sampleComponent = () = {
return isTrue ? True!
: falseID嵌套樣式問題!
使用表達(dá)式替換不必要的三元函數(shù)
const sampleComponent = () = {
return isTrue ? True!
: none/
const sampleComponent = () = {
return isTrue True!
需要注意的是如果isTrue 為 0 ,其實(shí)會(huì)轉(zhuǎn)換成 false,但是在頁(yè)面中顯示的時(shí)候,還是會(huì)返回0顯示到頁(yè)面中ID嵌套樣式問題。
多重嵌套判斷
// 問題代碼
const sampleComponent = () = {
return (
div
{flag flag2 !flag3
? flag4
? Blah
: flag5
? Meh
: Herp
: Derp
/div
解決方案:
最佳方案: 將邏輯移到子組件內(nèi)部
使用IIFE(Immediately-Invoked Function Expression 立即執(zhí)行函數(shù))
滿足條件的時(shí)候使用return強(qiáng)制跳出函數(shù)
const sampleComponent = () = {
const basicCondition = flag flag2 !flag3;
if (!basicCondition) return Derp
if (flag4) return Blah
if (flag5) return Meh
return Herp
setState異步性
在某些情況下,React框架出于性能優(yōu)化考慮,可能會(huì)將多次state更新合并成一次更新ID嵌套樣式問題。正因?yàn)槿绱?,setState實(shí)際上是一個(gè)異步的函數(shù)。 如果在調(diào)用setState()函數(shù)之后嘗試去訪問this.state,你得到的可能還是setState()函數(shù)執(zhí)行之前的結(jié)果。
但是,有一些行為也會(huì)阻止React框架本身對(duì)于多次state更新的合并,從而讓state的更新變得同步化ID嵌套樣式問題。 比如: eventListeners, Ajax, setTimeout 等等。
React框架之所以在選擇在調(diào)用setState函數(shù)之后立即更新state而不是采用框架默認(rèn)的方式,即合并多次state更新為一次更新,是因?yàn)檫@些函數(shù)調(diào)用(fetch,setTimeout等瀏覽器層面的API調(diào)用)并不處于React框架的上下文中,React沒有辦法對(duì)其進(jìn)行控制ID嵌套樣式問題。React在此時(shí)采用的策略就是及時(shí)更新,確保在這些函數(shù)執(zhí)行之后的其他代碼能拿到正確的數(shù)據(jù)(即更新過(guò)的state)。
解決setState函數(shù)異步的辦法?
根據(jù)React官方文檔,setState函數(shù)實(shí)際上接收兩個(gè)參數(shù),其中第二個(gè)參數(shù)類型是一個(gè)函數(shù),作為setState函數(shù)執(zhí)行后的回調(diào)ID嵌套樣式問題。通過(guò)傳入回調(diào)函數(shù)的方式,React可以保證傳入的回調(diào)函數(shù)一定是在setState成功更新this.state之后再執(zhí)行。
this.setState({count: 1}, () = {
console.log(this.state.count); // 1
React源碼中setState的實(shí)現(xiàn)
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的這兩個(gè)方法,和React底層的Virtual Dom(虛擬DOM樹)的diff算法有緊密的關(guān)系,所以真正決定同步還是異步的其實(shí)是Virtual DOM的diff算法ID嵌套樣式問題。
依賴注入
在React中,想做依賴注入(Dependency Injection)其實(shí)相當(dāng)簡(jiǎn)單ID嵌套樣式問題。可以通過(guò)props來(lái)進(jìn)行傳遞。但是,?cè)绻M件數(shù)量很多,并且組件嵌套層次很深的話,這種方式就不太合適。
高階組件
// inject.jsx
var title = 'React Dependency Injection';
export default function inject(Component) {
return class Injector extends React.Component {
render() {
return (
Component
{...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 (
header
EnhancedTitle /
/header
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 作為一個(gè)實(shí)驗(yàn)性質(zhì)的 API,直到 React v16.3.0 版本前都一直不被官方所提倡去使用,其主要原因就是因?yàn)樵谧咏M件中使用 Context 會(huì)破壞 React 應(yīng)用的分型架構(gòu)ID嵌套樣式問題。
這里的分形架構(gòu)指的是從理想的 React 應(yīng)用的根組件樹中抽取的任意一部分都仍是一個(gè)可以直接運(yùn)行的子組件樹ID嵌套樣式問題。在這個(gè)子組件樹之上再包一層,就可以將它無(wú)縫地移植到任意一個(gè)其他的根組件樹中。
但如果根組件樹中有任意一個(gè)組件使用了支持透?jìng)鞯?Context API,那么如果把包含了這個(gè)組件的子組件樹單獨(dú)拿出來(lái),因?yàn)槿鄙倭颂峁?Context 值的根組件樹,這時(shí)的這個(gè)子組件樹是無(wú)法直接運(yùn)行的ID嵌套樣式問題。
并且他有一個(gè)致命缺陷:任何一個(gè)中間傳遞的組件shouldComponentUpdate 函數(shù)返回false,組件都不會(huì)得到更新ID嵌套樣式問題。
新的Context Api
新的Context Api 采用聲明式的寫法,并且可以透過(guò)shouldComponentUpdate 函數(shù)返回false的組件繼續(xù)向下傳播,以保證目標(biāo)組件一定可以接收到頂層組件 Context 值的更新,一舉解決了現(xiàn)有 Context API 的兩大弊端,也終于成為了 React 中的第一級(jí)(first-class) APIID嵌套樣式問題。
新的 Context API 分為三個(gè)組成部分:
React.createContext 用于初始化一個(gè) ContextID嵌套樣式問題。
XXXContext.Provider作為頂層組件接收一個(gè)名為 value的 prop,可以接收任意需要被放入 Context 中的字符串,數(shù)字,甚至是函數(shù)ID嵌套樣式問題。
XXXContext.Consumer作為目標(biāo)組件可以出現(xiàn)在組件樹的任意位置(在 Provider 之后),接收 children prop,這里的 children 必須是一個(gè)函數(shù)(context = ())用來(lái)接收從頂層傳來(lái)的 ContextID嵌套樣式問題。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
return (
ThemeContext.Provider value="dark"
Toolbar /
/ThemeContext.Provider
function Toolbar(props) {
return (
div
ThemedButton /
/div
function ThemedButton(props) {
return (
ThemeContext.Consumer
{theme = Button {...props} theme={theme} /}
/ThemeContext.Consumer
事件處理中的this指向問題
class Switcher extends React.Component {
constructor(props) {
super(props);
this.state = { name: 'React in patterns' };
render() {
return (
button onClick={ this._handleButtonClick }
click me
/button
_handleButtonClick() {
console.log(`Button is clicked inside ${ this.state.name }`);
// 將導(dǎo)致
// Uncaught TypeError: Cannot read property 'state' of null
我們可以通過(guò)下面三種方式簡(jiǎn)單實(shí)現(xiàn)this指向的綁定:
在constructor 中事先綁定 this._buttonClick = this._handleButtonClick.bind(this);
調(diào)用時(shí)使用箭頭函數(shù) button onClick={ () = this._buttonClick() }
ES7中的綁定操作符 button onClick={ ::this._buttonClick() }
給setState傳入回調(diào)函數(shù)
setState() 不僅能接受一個(gè)對(duì)象,還能接受一個(gè)函數(shù)作為參數(shù)呢,該函數(shù)接受該組件前一刻的 state 以及當(dāng)前的 props 作為參數(shù),計(jì)算和返回下一刻的 stateID嵌套樣式問題。
// 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 Handler {...props} /
React style
組件分類
基礎(chǔ)組件ID嵌套樣式問題, 布局組件, 排版組件
給無(wú)狀態(tài)的純UI組件應(yīng)用樣式
請(qǐng)保持樣式遠(yuǎn)離那些離不開state的組件. 比如路由, 視圖, 容器, 表單, 布局等等不應(yīng)該有任何的樣式或者css class出現(xiàn)在組件上. 相反, 這些復(fù)雜的業(yè)務(wù)組件應(yīng)該有一些帶有基本功能的無(wú)狀態(tài)UI組件組成.
class SampleComponent extends Component {
render() {
return (
form onSubmit={this.handleSubmit}
Heading children='Sign In'/
Input
name='username'
value={username}
onChange={this.handleChange}/
Input
type='password'
name='password'
value={password}
onChange={this.handleChange}/
Button
type='submit'
children='Sign In'/
/form
// 表達(dá)組件(帶樣式)
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 (
button {...props} style={sx}/
樣式模塊(style module)
一般來(lái)說(shuō), 在組件內(nèi)寫死(hard code)樣式應(yīng)該是要被避免的. 這些有可能被不同的UI組件分享的樣式應(yīng)該被分開放入對(duì)應(yīng)的模塊中.
// 樣式模塊
export const white = '#fff';
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 (
button {...props} style={sx}/
樣式函數(shù)(Style Functions)
// Modular powers of two scale
const scale = [
0,
8,
16,
32,
64
// 通過(guò)這個(gè)函數(shù)去取得一部分的樣式
const createScaledPropertyGetter = (scale) = (prop) = (x) = {
return (typeof x === 'number' typeof scale[x] === 'number')
? {[prop]: scale[x]}
: null
const getScaledProperty = createScaledPropertyGetter(scale);
export const getMargin = getScaledProperty('margin');
export const getPadding = getScaledProperty('padding');
// 樣式函數(shù)的用法
const Box = ({
m,
p,
...props
const sx = {
...getMargin(m),
...getPadding(p)
return div {...props} style={sx}/
// 組件用法.
const Box = () = (
div
Box m={2} p={3}
A box with 16px margin and 32px padding
/Box
/div
常見小坑
state不更新ID嵌套樣式問題?
class SampleComponent extends Component {
// constructor function (or getInitialState)
constructor(props) {
super(props);
this.state = {
flag: false,
inputVal: props.inputValue
render() {
return div{this.state.inputVal AnotherComponent/}/div
這樣做的危險(xiǎn)在于, 有可能組件的props發(fā)生了改變但是組件卻沒有被更新. 新的props的值不會(huì)被React認(rèn)為是更新的數(shù)據(jù)因?yàn)闃?gòu)造器constructor或者getInitialState方法在組件創(chuàng)建之后不會(huì)再次被調(diào)用了,因此組件的state不再會(huì)被更新ID嵌套樣式問題。 要記住, State的初始化只會(huì)在組件第一次初始化的時(shí)候發(fā)生。
class SampleComponent extends Component {
// constructor function (or getInitialState)
constructor(props) {
super(props);
this.state = {
flag: false
render() {
return div{this.props.inputValue AnotherComponent/}/div
更干凈的render函數(shù)?
更干凈的render函數(shù)? 這個(gè)概念可能會(huì)有點(diǎn)讓人疑惑.
其實(shí)在這里干凈是指我們?cè)趕houldComponentUpdate這個(gè)生命周期函數(shù)里面去做淺比較, 從而避免不必要的渲染.
class Table extends PureComponent {
render() {
return (
div
{this.props.items.map(i =
Cell data={i} options={this.props.options || []}/
/div
這種寫法的問題在于{this.props.options || []} 這種寫法會(huì)導(dǎo)致所有的Cell都被重新渲染即使只有一個(gè)cell發(fā)生了改變. 為什么會(huì)發(fā)生這種事呢?
仔細(xì)觀察你會(huì)發(fā)現(xiàn), options這個(gè)數(shù)組被傳到了Cell這個(gè)組件上, 一般情況下, 這不會(huì)導(dǎo)致什么問題. 因?yàn)槿绻衅渌腃ell組件, 組件會(huì)在有props發(fā)生改變的時(shí)候淺對(duì)比props并且跳過(guò)渲染(因?yàn)閷?duì)于其他Cell組件, props并沒有發(fā)生改變). 但是在這個(gè)例子里面, 當(dāng)options為null時(shí), 一個(gè)默認(rèn)的空數(shù)組就會(huì)被當(dāng)成Props傳到組件里面去. 事實(shí)上每次傳入的[]都相當(dāng)于創(chuàng)建了新的Array實(shí)例. 在JavaScript里面, 不同的實(shí)例是有不同的實(shí)體的, 所以淺比較在這種情況下總是會(huì)返回false, 然后組件就會(huì)被重新渲染. 因?yàn)閮蓚€(gè)實(shí)體不是同一個(gè)實(shí)體. 這就完全破壞了React對(duì)于我們組件渲染的優(yōu)化.
const defaultval = []; // --- 也可以使用defaultProps
class Table extends PureComponent {
render() {
return (
div
{this.props.items.map(i =
Cell data={i} options={this.props.options || defaultval}/
/div
還是多次重新渲染
class App extends PureComponent {
render() {
return MyInput
onChange={e = this.props.update(e.target.value)}/;
class App extends PureComponent {
update(e) {
this.props.update(e.target.value);
render() {
return MyInput onChange={this.update.bind(this)}/;
在上面的兩個(gè)壞實(shí)踐中, 每次我們都會(huì)去創(chuàng)建一個(gè)新的函數(shù)實(shí)體. 和第一個(gè)例子類似, 新的函數(shù)實(shí)體會(huì)讓我們的淺比較返回false, 導(dǎo)致組件被重新渲染. 所以我們需要在更早的時(shí)候去bind我們的函數(shù).
class App extends PureComponent {
constructor(props) {
super(props);
this.update = this.update.bind(this);
update(e) {
this.props.update(e.target.value);
render() {
return MyInput onChange={this.update}/;
命名
引用命名
React模塊名使用帕斯卡命名ID嵌套樣式問題,實(shí)例使用駱駝式命名
// bad
import reservationCard from './ReservationCard';
// good
import ReservationCard from './ReservationCard';
// bad
const ReservationItem = ReservationCard /;
// good
const reservationItem = ReservationCard /;
高階模塊命名
// bad
export default function withFoo(WrappedComponent) {
return function WithFoo(props) {
return WrappedComponent {...props} foo /;
// good
export default function withFoo(WrappedComponent) {
function WithFoo(props) {
return WrappedComponent {...props} foo /;
const wrappedComponentName = WrappedComponent.displayName
|| WrappedComponent.name
|| 'Component';
WithFoo.displayName = `withFoo(${wrappedComponentName})`;
return WithFoo;
屬性命名
避免使用DOM相關(guān)的屬性來(lái)用作其他的用途ID嵌套樣式問題。
// bad
MyComponent style="fancy" /
// good
MyComponent variant="fancy" /
私有函數(shù)添加 _ 前綴?
在React模塊中,不要給所謂的私有函數(shù)添加 _ 前綴,本質(zhì)上它并不是私有的ID嵌套樣式問題。
為什么?_ 下劃線前綴在某些語(yǔ)言中通常被用來(lái)表示私有變量或者函數(shù)ID嵌套樣式問題。但是不像其他的一些語(yǔ)言,在JS中沒有原生支持所謂的私有變量,所有的變量函數(shù)都是共有的。盡管你的意圖是使它私有化,在之前加上下劃線并不會(huì)使這些變量私有化,并且所有的屬性(包括有下劃線前綴及沒有前綴的)都應(yīng)該被視為是共有的。
Ordering React 模塊生命周期
class extends React.Component 的生命周期函數(shù):
可選的 static 方法
constructor 構(gòu)造函數(shù)
getChildContext 獲取子元素內(nèi)容
componentWillMount 模塊渲染前
componentDidMount 模塊渲染后
componentWillReceiveProps 模塊將接受新的數(shù)據(jù)
shouldComponentUpdate 判斷模塊需不需要重新渲染
componentWillUpdate 上面的方法返回 trueID嵌套樣式問題, 模塊將重新渲染
componentDidUpdate 模塊渲染結(jié)束
componentWillUnmount 模塊將從DOM中清除, 做一些清理任務(wù)
點(diǎn)擊回調(diào)或者事件處理器 如 onClickSubmit() 或 onChangeDescription()
render 里的 getter 方法 如 getSelectReason() 或 getFooterContent()
可選的 render 方法 如 renderNavigation() 或 renderProfilePicture()
render render() 方法
如何定義 propTypes, defaultProps, contextTypes, 等等其他屬性...
import React from 'react';
import PropTypes from 'prop-types';
const propTypes = {
id: PropTypes.number.isRequired,
url: PropTypes.string.isRequired,
text: PropTypes.string,
const defaultProps = {
text: 'Hello World',
class Link extends React.Component {
static methodsAreOk() {
return true;
render() {
return a href={this.props.url} data-id={this.props.id}{this.props.text}/a;
Link.propTypes = propTypes;
Link.defaultProps = defaultProps;
export default Link;