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