函數式編程中一個核心概念之一就是純函數,若是一個函數知足一下幾個條件,就能夠認爲這個函數是純函數了:html
當一個函數知足以上條件的時候,就能夠認爲這個函數是純函數了。舉個栗子:react
// 非純函數 let payload = 0; function addOne(number) { ++payload; return number + payload; } addOne(1); // 2 addOne(1); // 3 addOne(1); // 4 // 純函數 function addOne(number) { return number + 1; } addOne(1); // 2 addOne(1); // 2 addOne(1); // 2
上面兩個栗子中,第一個就是典型的非純函數,當第一次執行 addOne(1)
其返回的值是 2
沒有錯,可是再次執行相同函數的時候,其返回的值再也不是 2
了,而是變成了 3
,對比上面列出的知足純函數的條件,就會發現:編程
addOne()
給定相同的輸入的時候沒有返回相同的輸出;addOne()
會產生反作用(會改變外部狀態 payload
);addOne()
依賴的外部狀態 payload
。而第二個栗子就是一個純函數,它既不依賴外部狀態也不會產生反作用,且當給定相同輸入的時候,老是返回相同的輸出(執行任意屢次 addOne(1)
老是返回 2
)。api
以上對純函數概念的一些簡單理解。less
官方給出的 React 的定義是:編程語言
A JavaScript library for building user interfaces.
即專一於構建 View
層的一個庫。React
的核心開發者之一的 Sebastian Markbåge 認爲:函數式編程
UI
只是把數據經過映射關係變成另外一種形式的數據。給定相同的輸入(數據)必然會有相同的輸出(UI
),即一個簡單的純函數。
React 中的函數式思想的具體體現函數
雖然說 View
層能夠當成是數據的另一種展示形式,但在實際的 React
開發中,除了數據的展現之外,更重要的是還有數據的交互,舉個栗子:post
import React, { Component } from 'react'; import { fetchPosts } from 'path/to/api'; export default class PostList extends Component { constructor() { this.state = { posts: [], }; } componentDidMount() { fetchPosts().then(posts => { this.setState({ posts: posts, }); }); } render() { return ( <ul> { this.state.posts.map(post => <li key={post.id} onClick={this.toggleActive}>{ post.title }</li>) } </ul> ); } toggleActive() { // } }
這個一個典型的渲染列表的栗子,在這個栗子中除了渲染 PostList
外,還進行了數據的獲取和事件的操做,也就意味着這個 PostList
組件不是一個「純函數」。嚴格意義上來講這個組件還不是一個可複用的組件,好比說有這樣一種業務場景,除了首頁有 PostList
組件之外,在我的頁面一樣有個 PostList
組件,UI
一致可是交互邏輯不一致,這種狀況下就沒法複用首頁的 PostList
組件了。爲了解決這個問題,咱們能夠再次抽離一個真正意義上可複用的 View
層,它有一下幾個特色:fetch
props
傳遞給子組件且是惟一數據來源),老是渲染相同的 UI
界面;能夠發現,這個上面所列出的知足純函數的條件很是類似,這種組件纔算是真正意義上的可複用的組件,好了,Talk is cheap, show me the code:
import React, { Component } from 'react'; import { fetchPosts } from 'path/to/api'; export default class PostListContainer extends Component { constructor() { this.state = { posts: [], }; } componentDidMount() { fetchPosts().then(posts => { this.setState({ posts, }); }); } render() { return ( <PostList posts={this.state.posts} toggleActive={this.toggleActive}></PostList> ); } toggleActive() { // } } // export default class PostList extends Component { render() { return (<ul>{ this.props.posts.map(post => <li key={post.id} onClick={this.props.toggleActive}>{ post.title }</li>) }</ul>); } }
經過這樣改造以後,本來數據交互和 UI
展現耦合則組件就被分爲了兩個職責明確的新組建,即 PostListContainer
負責數據獲取或點擊等交互邏輯,而 PostList
則真正意義上的只負責純粹的 View
層渲染。這種狀況下的 PostListContainer
被稱爲 Container Component
(容器組件),PostList
則被稱爲 Presentational Container
(展現組件)。再回到剛剛所假設的業務場景下,此時能夠經過建立不一樣的 Container Component
來處理不一樣的交互邏輯,而後把最終的數據經過 props
傳遞給子組件 PostList
,這樣的話不論是首頁仍是我的均可以真正複用 PostList
這個 Presentational Component
了。
再回過頭來思考一下前面提到的 Sebastian Markbåge 所認爲的理念:
UI
只是把數據經過映射關係變成另外一種形式的數據。給定相同的輸入(數據)必然會有相同的輸出(UI
),即一個簡單的純函數。
咱們能夠把這句話高度抽象成一個函數:data => View
,拿前面的 Presentational Component PostList
來講,其中 this.props.posts
就是 data => View
中的 data
,而整個渲染結果就是 View
,咱們再單獨分析一下這個組件:
import React, { Component } from 'react'; export default class PostList extends Component { render() { return (<ul>{ this.props.posts.map(post => <li key={post.id} onClick={this.props.toggleActive}>{ post.title }</li>) }</ul>); } }
其實會發現,儘管這個組件已經很簡單了,this.props.posts
傳入數據,而後渲染結果(同時還有綁定事件,可是沒有事件處理的具體邏輯),沒有再作其餘操做了。但咱們仔細思考的話,仍是會發現有兩個比較明顯的問題,一個是寫法上仍是典型的面向對象的方式來寫的;其次是該組件內部還有 this
關鍵字,爲何說在這裏使用關鍵字 this
是不合適的呢,由於 JavaScript
嚴格來講並非函數式編程語言,在 JavaScript
中 this
的指向又很是容易的被改變,因此依賴於 this
關鍵字的 data 是很是不穩定的。
好在以上兩個問題再 React
的 v0.14
版本中獲得瞭解決,在這次版本中 React
有一個新的特性叫 Stateless Functional Components
。什麼意思呢?咱們把上面的 PostList
組件以 Stateless Functional Components
的方式來從新編寫就會一目瞭然了:
const PostList = props => ( <ul> { props.posts.map(post => (<li key={ post.id } onClick={ props.toggleActive }>{ post.title }</li>)) } </ul> ); // 參數解構 const PostList = ({ posts, toggleActive }) => ( <ul> { posts.map(post => (<li key={ post.id } onClick={ toggleActive }>{ post.title }</li>)) } </ul> );
咱們會發現 Stateless Functional Components
完美的詮釋了前面所提到的 data => View
這個理念,不只數據輸入不依賴於 this
關鍵字,且書寫風格也更像函數式風格。
在平時的開發中,應該避免數據交互邏輯與數據渲染的過於耦合,嚴格區分 Container Component
和 Presentational Component
的職責不只能夠更容易的複用組件,並且也容易定位問題的所在。