react 设计模式与最佳实践
本文是閱讀米凱萊·貝爾托利 《React設計模式與最佳實踐》 一書的讀書筆記,支持作者請點這里購買。
Take is cheap, just show me the code.
廢話不少說,直接上干貨的哈。
關于 render 函數里面的條件判斷
在 React 里,有一種情況是,我們經常需要根據條件判斷決定是否渲染某些組件。就像是這樣:
<div>{ isLoggedIn ? <LogoutButton /> : <LoginButton /> }{ visible && <Modal /> } </div> 復制代碼當條件判斷變得更復雜的請求下,我們可以使用方法和計算屬性來取代三目運算和與或判斷。
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 這些輔助依賴了。 客官請看:
const isShowLoginButton = renderIf(this.isLoggedIn && this.isAuthed ) return (<div>{ isShowLoginButton(<LoginButton />) } {/* 完了結果 LogoutButton 我還需要另外寫一個 isShowLogoutButton 的 renderIf 去判斷顯示與否嗎 */} </div>) 復制代碼然后 render-only-if 本質上是一個高階函數,它形式上會比 render-if 優雅些。
const LoginButtonOnlyIf = onlyIf(({ isLoggedIn && isAuthed }) => {return isLoggedIn && isAuthed} )(LoginButton)return (<LoginButtonOnlyIf isLoggedIn={isLoggedIn}isAuthed={isAuthed}/> ) 復制代碼總結:
- 如果只是簡單的條件判斷,三目和與或運算符已經滿足大多數人的需求;
- 如果想讓關注點分離,renderIf 是個不錯的注意;
- 最后如果你希望你的判斷邏輯能夠被復用(就好像多個頁面多個組件都需要用到判斷登錄狀態和用戶權限的邏輯),可以使用 onlyIf 構建項目內可復用的高階組件。
然后我們最后看看 jsx-control-statements 這個惡心東西的用法:
<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> 復制代碼開發可復用組件
這是關于性能和可維護性的課題吶。
始終牢記,設置狀態會觸發組件重新渲染。因此,應該只將渲染方法要用到的值保存在狀態中。
以下是 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> 復制代碼最后我們來談談 CSS
首先,簡單粗暴的我們可以在 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 和進行延伸閱讀的。也歡迎大家關注督促我下期更文。
總結
以上是生活随笔為你收集整理的react 设计模式与最佳实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python零基础学习笔记(十三)——
- 下一篇: asp.net ajax控件工具集 Au