摘要:每當(dāng)?shù)闹蹈淖兒螅覀冎恍枰匦抡{(diào)用方法即可現(xiàn)在,讓我們來(lái)實(shí)現(xiàn)一個(gè)類似風(fēng)格的歸約函數(shù),以不斷的遞增。歸約函數(shù)是不允許修改當(dāng)前狀態(tài)的,所有最簡(jiǎn)單的實(shí)現(xiàn)方式就是。
原文:Functional Components with React stateless functions and Ramda
閱讀本文需要的知識(shí)儲(chǔ)備:
函數(shù)式編程基本概念(組合、柯里化、透鏡)
React 基本知識(shí)(組件、狀態(tài)、屬性、JSX)
ES6 基本知識(shí)(class、箭頭函數(shù))
React 無(wú)狀態(tài)函數(shù)React 組件最常見(jiàn)的定義方法:
const List = React.createClass({ render: function() { return (
或者使用 ES6 類語(yǔ)法:
class List extends React.Component { render() { return (
又或者使用普通的 JS 函數(shù):
// 無(wú)狀態(tài)函數(shù)語(yǔ)法 const List = function(children) { return (
React 官方文檔對(duì)這種組件做了以下說(shuō)明:
這種簡(jiǎn)化的組件 API 適用于僅依賴屬性的純函數(shù)組件。這些組件不允許擁有內(nèi)部狀態(tài),不會(huì)生成組件實(shí)例,也沒(méi)有組件的生命周期方法。它們只對(duì)輸入進(jìn)行純函數(shù)轉(zhuǎn)換。不過(guò)開(kāi)發(fā)者仍然可以為它們指定 .propTypes 和 .defaultProps,只需要設(shè)置為函數(shù)的屬性就可以了,就跟在 ES6 類上設(shè)置一樣。
同時(shí)也說(shuō)到:
理想情況下,大部分的組件都應(yīng)該是無(wú)狀態(tài)的函數(shù),因?yàn)樵谖磥?lái)我們可能會(huì)針對(duì)這類組件做性能優(yōu)化,避免不必要的檢查和內(nèi)存分配。所以推薦大家盡可能的使用這種模式來(lái)開(kāi)發(fā)。
是不是覺(jué)得挺有趣的?
React 社區(qū)似乎更加關(guān)注通過(guò) class 和 createClass 方式來(lái)創(chuàng)建組件,今天讓我們來(lái)嘗鮮一下無(wú)狀態(tài)組件。
App 容器首先讓我們來(lái)創(chuàng)建一個(gè)函數(shù)式 App 容器組件,它接受一個(gè)表示應(yīng)用狀態(tài)的對(duì)象作為參數(shù):
import React from "react"; import ReactDOM from "react-dom"; const App = appState => ();App name
Some children here...
然后,定義一個(gè) render 方法,作為 App 函數(shù)的屬性:
import React from "react"; import ReactDOM from "react-dom"; import R from "ramda"; const App = appState => (); App.render = R.curry((node, props) => ReactDOM.render(App name
Some children here...
, node)); export default App;
等等!有點(diǎn)看不明白了!
為什么我們需要一個(gè)柯里化的渲染函數(shù)?又為什么渲染函數(shù)的參數(shù)順序反過(guò)來(lái)了?
別急別急,這里唯一要說(shuō)明的是,由于我們使用的是無(wú)狀態(tài)組件,所以狀態(tài)必須由其它地方來(lái)維護(hù)。也就是說(shuō),狀態(tài)必須由外部維護(hù),然后通過(guò)屬性的方式傳遞給組件。
讓我們來(lái)看一個(gè)具體的計(jì)時(shí)器例子。
一個(gè)簡(jiǎn)單的計(jì)時(shí)器組件只接受一個(gè)屬性 secondsElapsed:
import React from "react"; export default ({ secondsElapsed }) => (Seconds Elapsed: {secondsElapsed});
把它添加到 App 中:
import React from "react"; import ReactDOM from "react-dom"; import R from "ramda"; import Timer from "./timer"; const App = appState => (); App.render = R.curry((node, props) => ReactDOM.render(App name
, node)); export default App;
最后,創(chuàng)建 main.js 來(lái)渲染 App:
import App from "./components/app"; const render = App.render(document.getElementById("app")); let appState = { secondsElapsed: 0 }; //first render render(appState); setInterval(() => { appState.secondsElapsed++; render(appState); }, 1000);
在進(jìn)一步說(shuō)明之前,我想說(shuō),appState.secondElapsed++ 這種修改狀態(tài)的方式讓我覺(jué)得非常不爽,不過(guò)稍后我們會(huì)使用更好的方式來(lái)實(shí)現(xiàn)。
這里我們可以看出,render 其實(shí)就是用新屬性來(lái)重新渲染組件的語(yǔ)法糖。下面這行代碼:
const render = App.render(document.getElementById(‘a(chǎn)pp’));
會(huì)返回一個(gè)具有 (props) => ReactDOM.render(...) 函數(shù)簽名的函數(shù)。
這里并沒(méi)有什么太難理解的內(nèi)容。每當(dāng) secondsElapsed 的值改變后,我們只需要重新調(diào)用 render 方法即可:
setInterval(() => { appState.secondsElapsed++; render(appState); }, 1000);
現(xiàn)在,讓我們來(lái)實(shí)現(xiàn)一個(gè)類似 Redux 風(fēng)格的歸約函數(shù),以不斷的遞增 secondsElapsed。歸約函數(shù)是不允許修改當(dāng)前狀態(tài)的,所有最簡(jiǎn)單的實(shí)現(xiàn)方式就是 currentState -> newState。
這里我們使用 Ramda 的透鏡(Lens)來(lái)實(shí)現(xiàn) incSecondsElapsed 函數(shù):
const secondsElapsedLens = R.lensProp("secondsElapsed"); const incSecondsElapsed = R.over(secondsElapsedLens, R.inc); setInterval(() => { appState = incSecondsElapsed(appState); render(appState); }, 1000);
第一行代碼中,我們創(chuàng)建了一個(gè)透鏡:
const secondsElapsedLens = R.lensProp("secondsElapsed");
簡(jiǎn)單來(lái)說(shuō),透鏡是一種專注于給定屬性的方式,而不關(guān)心該屬性到底是在哪個(gè)對(duì)象上,這種方式便于代碼復(fù)用。當(dāng)我們需要把透鏡應(yīng)用于對(duì)象上時(shí),可以有以下操作:
View
R.view(secondsElapsedLens, { secondsElapsed: 10 }); //=> 10
Set
R.set(secondsElapsedLens, 11, { secondsElapsed: 10 }); //=> 11
以給定函數(shù)來(lái)設(shè)置
R.over(secondsElapsedLens, R.inc, { secondsElapsed: 10 }); //=> 11
我們實(shí)現(xiàn)的 incSecondsElapsed 就是對(duì) R.over 進(jìn)行局部應(yīng)用的結(jié)果。
const incSecondsElapsed = R.over(secondsElapsedLens, R.inc);
該行代碼會(huì)返回一個(gè)新函數(shù),一旦調(diào)用時(shí)傳入 appState,就會(huì)把 R.inc 應(yīng)用在 secondsElapsed 屬性上。
需要注意的是,Ramda 從來(lái)都不會(huì)修改對(duì)象,所以我們需要自己來(lái)處理臟活:
appState = incSecondsElapsed(appState);
如果想支持 undo/redo ,只需要維護(hù)一個(gè)歷史數(shù)組記錄下每一次狀態(tài)即可,或者使用 Redux 。
目前為止,我們已經(jīng)品嘗了柯里化和透鏡,下面讓我們繼續(xù)品嘗組合。
組合 React 無(wú)狀態(tài)組件當(dāng)我第一次讀到 React 無(wú)狀態(tài)組件時(shí),我就在想能否使用 R.compose 來(lái)組合這些函數(shù)呢?答案很明顯,當(dāng)然是 YES 啦:)
讓我們從一個(gè) TodoList 組件開(kāi)始:
const TodoList = React.createClass({ render: function() { const createItem = function(item) { return (
現(xiàn)在問(wèn)題來(lái)了,TodoList 能否通過(guò)組合更小的、可復(fù)用的組件來(lái)實(shí)現(xiàn)呢?當(dāng)然,我們可以把它分割成 3 個(gè)小組件:
容器
const Container = children => ();{children}
列表
const List = children => (
列表項(xiàng)
const ListItem = ({ id, text }) => (
現(xiàn)在,我們來(lái)一步一步看,請(qǐng)一定要在理解了每一步之后才往下看:
Container(Hello World!
); /** ***/ Container(List(**Hello World!
*
沒(méi)有什么太特別的,只不過(guò)是一步一步的傳參調(diào)用。
接著,讓我們來(lái)做一些組合的練習(xí):
R.compose(Container, List)(
發(fā)現(xiàn)了沒(méi)!TodoList 組件已經(jīng)被表示成了 Container、List 和 ListItem 的組合了:
const TodoList = R.compose(Container, List, ListItem);
等等!TodoList 這個(gè)組件只接受一個(gè) todo 對(duì)象,但是我們需要的是映射整個(gè) todos 數(shù)組:
const mapTodos = function(todos) { return todos.map(function(todo) { return ListItem(todo); }); }; const TodoList = R.compose(Container, List, mapTodos); const mock = [ {id: 1, text: "One"}, {id: 1, text: "Two"}, {id: 1, text: "Three"} ]; TodoList(mock); /** ***/***
*- * One *
*- * Two *
*- * Three *
*
能否以更函數(shù)式的方式簡(jiǎn)化 mapTodos 函數(shù)?
// 下面的代碼 return todos.map(function(todo) { return ListItem(todo); }); // 等效于 return todos.map(ListItem); // 所以變成了 const mapTodos = function(todos) { return todos.map(ListItem); }; // 等效于使用 Ramda 的方式 const mapTodos = function(todos) { return R.map(ListItem, todos); }; // 注意 Ramda 的兩個(gè)特點(diǎn): // - Ramda 函數(shù)默認(rèn)都支持柯里化 // - 為了便于柯里化,Ramda 函數(shù)的參數(shù)進(jìn)行了特定排列, // 待處理的數(shù)據(jù)通常放在最后 // 因此: const mapTodos = R.map(ListItem); //此時(shí)就不再需要 mapTodos 了: const TodoList = R.compose(Container, List, R.map(ListItem));
噠噠噠!完整的 TodoList 實(shí)現(xiàn)代碼如下:
import React from "React"; import R from "ramda"; const Container = children => (); const List = children => ({children}
其實(shí),還少了一樣?xùn)|西,不過(guò)馬上就會(huì)加上。在那之前讓我們先來(lái)做些準(zhǔn)備:
添加測(cè)試數(shù)據(jù)到應(yīng)用狀態(tài)
let appState = { secondsElapsed: 0, todos: [ {id: 1, text: "Buy milk"}, {id: 2, text: "Go running"}, {id: 3, text: "Rest"} ] };
添加 TodoList 到 App
import TodoList from "./todo-list"; const App = appState => ();App name
TodoList 接受的是一個(gè) todos 數(shù)組,但是這里卻是:
我們把列表傳遞作為一個(gè)屬性,所以等效于:
TodoList({todos: appState.todos});
因此,我們必須修改 TodoList,以便讓它接受一個(gè)對(duì)象并且取出 todos 屬性:
const TodoList = R.compose(Container, List, R.map(ListItem), R.prop("todos"));
這里并沒(méi)有什么高深技術(shù)。僅僅是從右到左的組合,R.prop("todos") 會(huì)返回一個(gè)函數(shù),調(diào)用該函數(shù)會(huì)返回其作為的參數(shù)對(duì)象的 todos 屬性,接著把該屬性值傳遞給 R.map(ListItem),如此往復(fù):)
以上就是本文的嘗鮮內(nèi)容。希望能對(duì)大家有所幫助,這僅僅是我基于 React 和 Ramda 做的一部分實(shí)驗(yàn)。未來(lái),我會(huì)努力嘗試覆蓋高階組件和使用 Transducer 來(lái)轉(zhuǎn)換無(wú)狀態(tài)函數(shù)。
完整源碼,線上演示代碼(譯者新增)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/96005.html
摘要:如果這個(gè)結(jié)構(gòu)非常復(fù)雜,那么想要安全優(yōu)雅地取出一個(gè)值,也并非簡(jiǎn)單。這是為了在對(duì)象中相關(guān)取值的過(guò)程,需要驗(yàn)證每一個(gè)和的存在性。并且這個(gè)數(shù)據(jù)結(jié)構(gòu)必然是動(dòng)態(tài)生成的,存在有時(shí)有時(shí)的情況。在測(cè)試過(guò)程中,很難復(fù)現(xiàn)。 古有趙子龍面對(duì)沖鋒之勢(shì),有進(jìn)無(wú)退,陷陣之志,有死無(wú)生的局面,能萬(wàn)軍叢中取敵將首級(jí)。在我們的Javascript中,往往用對(duì)象(Object)來(lái)存儲(chǔ)一個(gè)數(shù)據(jù)結(jié)構(gòu)。如果這個(gè)結(jié)構(gòu)非常復(fù)雜,那么...
摘要:如果這個(gè)結(jié)構(gòu)非常復(fù)雜,那么想要安全優(yōu)雅地取出一個(gè)值,也并非簡(jiǎn)單。這是為了在對(duì)象中相關(guān)取值的過(guò)程,需要驗(yàn)證每一個(gè)和的存在性。并且這個(gè)數(shù)據(jù)結(jié)構(gòu)必然是動(dòng)態(tài)生成的,存在有時(shí)有時(shí)的情況。在測(cè)試過(guò)程中,很難復(fù)現(xiàn)。 古有趙子龍面對(duì)沖鋒之勢(shì),有進(jìn)無(wú)退,陷陣之志,有死無(wú)生的局面,能萬(wàn)軍叢中取敵將首級(jí)。在我們的Javascript中,往往用對(duì)象(Object)來(lái)存儲(chǔ)一個(gè)數(shù)據(jù)結(jié)構(gòu)。如果這個(gè)結(jié)構(gòu)非常復(fù)雜,那么...
摘要:為了盡可能提升互通性,已經(jīng)成為函數(shù)式編程庫(kù)遵循的實(shí)際標(biāo)準(zhǔn)。與輕量級(jí)函數(shù)式編程的概念相反,它以火力全開(kāi)的姿態(tài)進(jìn)軍的函數(shù)式編程世界。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個(gè)流淌著滬江血液的純粹工程:認(rèn)真,是 HTML 最堅(jiān)實(shí)的梁柱;分享,是 CSS 里最閃耀的一瞥;總結(jié),...
摘要:頁(yè)面調(diào)試騰訊開(kāi)發(fā)維護(hù)的代碼調(diào)試發(fā)布,錯(cuò)誤監(jiān)控上報(bào),用戶問(wèn)題定位。同樣是由騰訊開(kāi)發(fā)維護(hù)的代碼調(diào)試工具,是針對(duì)移動(dòng)端的調(diào)試工具。前端業(yè)務(wù)代碼工具庫(kù)。動(dòng)畫(huà)庫(kù)動(dòng)畫(huà)庫(kù),也是目前通用的動(dòng)畫(huà)庫(kù)。 本人微信公眾號(hào):前端修煉之路,歡迎關(guān)注 本篇文章整理自己使用過(guò)的和看到過(guò)的一些插件和工具,方便日后自己查找和使用。 另外,感謝白小明,文中很多的工具來(lái)源于此。 彈出框 layer:http://layer....
閱讀 2168·2023-04-26 00:43
閱讀 2688·2021-11-22 15:22
閱讀 3822·2021-11-11 16:55
閱讀 972·2021-11-04 16:06
閱讀 1790·2019-08-30 14:12
閱讀 1004·2019-08-30 14:02
閱讀 3374·2019-08-29 17:05
閱讀 1421·2019-08-29 12:27