本文是閱讀米凱萊·貝爾託利 《React設計模式與最佳實踐》 一書的讀書筆記,支持做者請點這裏購買。javascript
Talk is cheap, just show me the code.css
廢話很多說,直接上乾貨的哈。html
在 React 裏,有一種狀況是,咱們常常須要根據條件判斷決定是否渲染某些組件。就像是這樣:前端
<div>
{ isLoggedIn ? <LogoutButton /> : <LoginButton /> }
{ visible && <Modal /> }
</div>
複製代碼
當條件判斷變得更復雜的請求下,咱們可使用方法和計算屬性來取代三目運算和與或判斷。java
handleShowLoginButton() {
return this.isLoggedIn && this.isAuthed;
}
get getVisible() {
return this.visible && this.displayMode === "normal"
}
render() {
return (<div> { handleShowLoginButton() ? <LogoutButton /> : <LoginButton /> } { getVisible && <Modal /> } </div>)
}
複製代碼
而後黑科技來了,當咱們想要把這些判斷邏輯從 render 渲染函數裏抽離出來以讓渲染函數只負責渲染的時候。咱們就須要用到 render-if
render-only-if
jsx-control-statements
這些輔助依賴了。 客官請看:react
const isShowLoginButton = renderIf(
this.isLoggedIn && this.isAuthed
)
return (<div> { isShowLoginButton(<LoginButton />) } {/* 完告終果 LogoutButton 我還須要另外寫一個 isShowLogoutButton 的 renderIf 去判斷顯示與否嗎 */} </div>)
複製代碼
而後 render-only-if 本質上是一個高階函數,它形式上會比 render-if 優雅些。git
const LoginButtonOnlyIf = onlyIf(
({ isLoggedIn && isAuthed }) => {
return isLoggedIn && isAuthed
}
)(LoginButton)
return (
<LoginButtonOnlyIf isLoggedIn={isLoggedIn} isAuthed={isAuthed} /> ) 複製代碼
總結:github
而後咱們最後看看 jsx-control-statements 這個噁心東西的用法:npm
<If condition={this.handleShowLoginButton}>
<LoginButton />
</If>
<When condition={this.handleShowLoginButton}>
<LoginButton />
</When>
<When condition={!this.handleShowLoginButton}>
<LogoutButton /> // => 好了這下終於看見咱們的 LogoutButton 出現了
</When>
<Otherwise>
<p>oops.. no condition matched.</p>
</Otherwise>
<ul>
<For each="resultItem" of={this.resultList}>
<li>{resultItem.name}</li>
</For>
// => {resultList.map(resultItem => <li>{resultItem.name}</li>)}
</ul>
複製代碼
這是關於性能和可維護性的課題吶。json
始終牢記,設置狀態會觸發組件從新渲染。所以,應該只將渲染方法要用到的值保存在狀態中。
如下是 Dan Abramov (我並不知道他是誰) 建立的幫助咱們作出正確狀態選擇的步驟:
function shouldIKeepSomethingInReactState() {
if (canICalculateItFromProps()) {
// 不要把 props 屬性直接在 state 狀態中使用,
// 應該直接在 render() 函數裏計算使用它們
return false
}
if (!amIUsingItInRenderMethod()) {
// 不要把沒有參與渲染的數據放進 state 狀態裏,
// 換句話說就是隻有須要涉及到組件 render 渲染更新的數據才放到 state 裏
return false
}
// 除了以上狀況,均可以使用狀態。
return true;
}
複製代碼
關於 prop 類型檢驗,React 提供了組件的 propTypes
屬性給咱們使用:
const Button = ({text}) => <button>{text}</button>
Button.propTypes = {
text: React.PropTypes.string
}
複製代碼
但其實在 TypeScript 的世界裏,咱們直接可使用模板類的形式給咱們 React 組件聲明 prop 屬性接口:
interface IButtonProps = {
text: string;
}
class ButtonClass extend React.Component<IButtonProps, IButtonStates> {}
// => 順帶連 state 屬性檢驗也能夠加進來
複製代碼
接下來爲組件自動生成文檔,使用 react-docgen
這個工具。
import React from 'react';
/**
* Sheet 組件
*/
const Sheet = ({title}) => <div>{title}</div>
Sheet.prototype = {
/**
* Sheet 標題
*/
title: React.PropTypes.string
}
複製代碼
運行 react-docgen Sheet.js
後結果產出以下 json 描述:
{
"description": "Sheet 組件",
"displayName": "Sheet",
"methods": [],
"props": {
"title": {
"type": {
"name": "string"
},
"required": false,
"description": "Sheet 標題"
}
}
}
複製代碼
把這個 json 文件做爲團隊前端文檔項目的輸入,就能夠自動化地生成可用的組件文檔說明啦啦啦。
好了,接下來祭出業內大殺器 storybook。
npm i --save @kadira/react-storybook-addon
複製代碼
(貌似 @kadira/react-storybook-addon 已經報廢了,建議小夥伴仍是在官網按照文檔寫本身的 storybook 吧)
故事文檔放在 stories
的文件夾中,咱們在 stories 文件夾下建立 sheet.js 定義咱們上面定義組件的故事文檔。
// => stories/sheet.js
import React from 'react';
import Sheet from '../src/components/Sheet';
import { storiesOf } from '@kadira/storybook'
storiesOf('Sheet', module)
.add('沒有 title 屬性的 Sheet 的故事..', () => (
<Sheet/>
))
複製代碼
可是咱們要寫故事還得在根目錄下先來配置好 storybook :
// => .storybook/config.js => 根目錄下建立 .storybook 文件夾
import { configure } from '@kadira/storybook';
function loadStories() {
require('../src/stories/sheet')
}
configure(loadStories, module)
複製代碼
最後咱們給咱們的 package.json 加上 script 來運行咱們的故事。
"storybook": "start-storybook -p 9001"
複製代碼
運行以後在 9001 端口就能夠看到故事文檔啦啦啦。
關於容器組件和傻瓜組件,我在這裏就不說了哈。畢竟是初級內容,很差濫竽充數。咱們直接直奔主題。
好比,最簡單的,實現一個給組件加上類名的高階組件:
const withClassName = Component => props => (
<Component {...props} className="my-class" /> ) 複製代碼
上面只是動了一個 prop 而已哈,實際上除了 prop 其餘的一切組件屬性咱們均可以動哈。
const withTimer = Component => (
class extends React.Component {
constructor(props) {
super(props)
this.state = {
timer: null
}
}
componentDidMount() {
this.timer = setTimeInterval(() => {
console.log('每一個1.5s打印一第二天志哈哈哈')
}, 1500)
}
componentWillUnmount() {
clearInterval(this.timer)
}
render() {
// => 原封不動把接收到的 props 傳給 Component
// state 傳入 Compnent 實際上是可選項,根據實際需求決定
return <Component {...this.props} {...this.state} /> } } ) // => 而後咱們就能夠給普通的組件加上定時打印日誌的功能啦 const SheetWithTimer = withTimer(Sheet); 複製代碼
而後 recompose 這個庫已經幫咱們提供了一些很實用場景的高階組件,開箱即用哈。
接下來咱們來搞點噱頭,看看函數子組件怎麼玩。首先,咱們上面那個 withClassName 顯然太 low 了,竟然 className 是寫死的!?凡事都不要寫死,需求之後分分鐘給你改。
顯然,咱們須要在 withClassName 組件裏面再作多一層邏輯,判斷好後再動態傳 className 給子組件。這個時候咱們爲了搞噱頭,決定採用函數子組件的模式。
const withClassName = ({children}) => children('my-class'); // => wft,這裏 'my-class' 還不照樣是寫死的...
<withClassName>
{(classname) => <Component className={classname} />} </withClassName>
複製代碼
而後,咱們就看到了無限可能... 雖然 withClassName 如今仍是個無狀態組件哈,可是咱們徹底能夠像 withTimer 組件那樣給它加上生命鉤子和函數方法還有狀態。而後在 render 裏不一樣的是(咱們不直接使用 <Component /> 而是執行 children()):
render() {
return <Component {...props} /> // => 通常作法 renturn {children(props)} // => 函數子組件作法 } 複製代碼
或許更貼切的例子是高階組件須要作 http 請求的場景吧,把請求回來的數據再傳入子組件進行渲染。
<Fetch url="...">
{data => <MyComp data={data} />} </Fetch>
複製代碼
首先,簡單粗暴的咱們能夠在 html 元素裏直接寫 style,在 jsx 的世界裏是長這樣的:
<div style={{ fonSize: this.state.fontSize }} />
複製代碼
而後 style 行內樣式有個缺點,就是你不能直接寫媒體查詢
和僞類僞元素
,固然動畫
(插播小廣告:動畫 react 庫請使用 react-motion)你也無法寫。
因此 Radium 應運而生。
有了 Radium ,你能夠任性地這樣操做:
import radium from 'radium'
const myStyle = {
fontSize: '12px',
':hover': {
color: '#abc'
},
'@media (min-width: 720px)': {
color: '#121212'
}
}
const Div = () => <div style={myStyle} />
export default radium(Div);
複製代碼
固然使用媒體查詢你還須要在最外層保一個 styleroot 元素,要否則 Radium 哪知道你媒體查詢根元素在哪裏喲。
import { StyleRoot } from 'radium'
class App extends Component {
render() {
return (
<StyleRoot> <router-view /> </StyleRoot> ) } } 複製代碼
固然咱們徹底能夠不使用行內樣式,而是使用基於 className 的 css 模塊。
import styles from './index.less'
render() {
return {
<div className={styles.myDiv} />
}
}
複製代碼
/* index.less */
.myDiv {
font-size: 12px
}
/* 默認模塊會生成一堆咱們看不懂的英文類名,若是想要讓類名不做用在局部而是全局,可使用 :global */
:global .myGlobalDiv {
font-size: 15px
}
/* 咱們還可使用 composes 把其餘類的樣式混進來 */
.myDivCop {
composes: .myDiv;
color: '#101010'
}
複製代碼
再有,若是你的 className 不想寫 style.[類名] 的形式而是想直接寫字符串類名的形式,你能夠借用 react-css-modules 這個庫。
而後這種姿式使用:
import cssModules from 'react-css-modules'
import styles from './index.less'
class DivComp extends Component {
render() {
return (
<div className='myDiv' /> ) } } export cssModules(DivComp, styles) 複製代碼
而後還有個多是之後趨勢的叫 styled-components 的傢伙,由於樓主實在是學不動了因此這裏就不展開講了哈。
開玩笑的哈,歡迎評論區留言告訴我你想要閱讀的內容主題,我會只選我會的,不會的都不選哈哈哈 我會盡可能抽出時間來擼 demo 和進行延伸閱讀的。也歡迎你們關注督促我下期更文。