翻譯|Functional Components with React stateless functions and Ramda

原文在這裏.java

什麼是 React stateless function?

es6的語法node

class List extends React.Component {
  render() {
    return (<ul>{this.props.children}</ul>);
  }
}
複製代碼

簡單的 javascripg函數也能夠!react

//Stateless function syntax
const List = function(children) {
  return (<ul>{children}</ul>);
};

//ES6 arrow syntax
const List = (children) => (<ul>{children}</ul>);
複製代碼

完全的模板,沒有本身任何的數據,也沒有生命週期方法. 純粹依賴於輸入.es6

首先來定義一個 App Container

目的是最爲一個函數接收 app sate 對象數組

import React from 'react';
import ReactDOM from 'react-dom';

const App = appState => (<div className="container"> <h1>App name</h1> <p>Some children here...</p> </div>);
//這裏定義了渲染的方法,做爲 APP函數的屬性,而且是柯理化的, 等待傳入 dom 元素
App.render = R.curry((node, props) => ReactDOM.render(<App {...props}/>, node)); export default App; 複製代碼

在純函數中,state 必需要在外部管理,而後以 props 的形式傳遞給組件. 下面看看這個解釋的例子bash

Stateless Timer component

簡單的 timer 組件只接受 secondsElapsed 參數:閉包

import React from 'react';

export default ({ secondsElapsed }) => (<div className="well"> Seconds Elapsed: {secondsElapsed} </div>);
複製代碼

添加到 APP 中app

import React from 'react';
import ReactDOM from 'react-dom';
import R from 'ramda';
import Timer from './timer';

const App = appState => (<div className="container">
  <h1>App name</h1>
  //Timer 只從父組件接受 props 做爲本身的數據
  <Timer secondsElapsed={appState.secondsElapsed} />
</div>);

App.render = R.curry((node, props) => ReactDOM.render(<App {...props}/>, node));

export default App;
複製代碼

最後建立main.js 文件,啓動渲染過程less

import App from './components/app'; //導入容器組件

// 咱們已經有了柯理化的方法
//App.render = R.curry((node, props) => ReactDOM.render(<App {...props}/>, node));
//配置好渲染的目標元素
const render = App.render(document.getElementById('app'));
//state 初始值
let appState = {
  secondsElapsed: 0
};

//first render 首次渲染
render(appState);
//屢次重複渲染
setInterval(() => {
  appState.secondsElapsed++;
  render(appState);
}, 1000);
複製代碼

對於上面的代碼, 變化的是組件的 state, 渲染的目標元素是一直不變的, 因此咱們用柯理化配置好一個工廠函數dom

//閉包再工做!
const render = App.render(document.getElementById(‘app’));
複製代碼

柯理化返回的函數,等待傳入 props

(props) => ReactDOM.render(...)
複製代碼

只要 State發生變化,咱們須要渲染時,只須要傳遞 state 就能夠了

setInterval(() => {
  appState.secondsElapsed++;
  render(appState);
}, 1000);
複製代碼

每一秒鐘, secondsElapsed 屬性會遞增1, 而後做爲參數傳遞給 render 函數

如今能夠實現 Redux 風格的 reduce 函數, reduce式的函數不能突變當前值

currentState->newState
複製代碼

使用 Radma 的 Lenses 來實現

const secondsElapsedLens = R.lensProp('secondsElapsed');
const incSecondsElapsed = R.over(secondsElapsedLens, R.inc);

setInterval(() => {
  appState = incSecondsElapsed(appState);
  render(appState);
}, 1000);
複製代碼

首先建立 Lens:

const secondsElapsedLens = R.lensProp('secondsElapsed');
複製代碼

lens能夠聚焦於給定的屬性,不會針對特定的對象, 因此能夠重用.

  • View
R.view(secondsElapsedLens, { secondsElapsed: 10 });  //=> 10
複製代碼
  • Set
R.set(secondsElapsedLens, 11, { secondsElapsed: 10 });  //=> 11
複製代碼
  • 用給定的函數 Set
R.over(secondsElapsedLens, R.inc, { secondsElapsed: 10 });  //=> 11
複製代碼

inSecondElapsed reducer 是一個偏應用函數(partial application), 這一行

const incSecondsElapsed = R.over(secondsElapsedLens, R.inc);
複製代碼

會返回一個新的函數,一旦用appState 調用, 就會應用 R.inc在 lensed prop secondElapsed 上.

appState=incSecondElapsed(appState)
複製代碼

組合 React stateless components

開篇提到,React 組件能夠做爲函數, 那麼能夠用 R.compose來 compose 這些函數嗎? 固然是能夠的

用 React.createClass 是這樣的:

const TodoList = React.createClass({
  render: function() {
    const createItem = function(item) {
      return (<li key={item.id}>{item.text}</li>);
    };

    return (<div className="panel panel-default"> <div className="panel-body"> <ul> {this.props.items.map(createItem)} </ul> </div> </div>);
  }
});
複製代碼

如今問題是: TodoList 能夠由小的可重用部分 composition 而成嗎? 能夠的. 能夠分爲三個更小的組件

  • 容器組件
const Container = children => (<div className="panel panel-default"> <div className="panel-body"> {children} </div> </div>);
複製代碼
  • 列表組件
const List = children => (<ul> {children} </ul>);
複製代碼
  • 列表項組件
const ListItem = ({ id, text }) => (<li key={id}> <span>{text}</span> </li>);
複製代碼

如今一步一動,看看每一步的輸出

Container(<h1>Hello World!</h1>);

/**
 *  <div className="panel panel-default">
 *    <div className="panel-body">
 *      <h1>Hello World!</h1>
 *    </div>
 *  </div>
 */

Container(List(<li>Hello World!</li>));

/**
 *  <div className="panel panel-default">
 *    <div className="panel-body">
 *      <ul>
 *        <li>Hello World!</li>
 *      </ul>
 *    </div>
 *  </div>
 */

const TodoItem = {
  id: 123,
  text: 'Buy milk'
};
Container(List(ListItem(TodoItem)));

/**
 *  <div className="panel panel-default">
 *    <div className="panel-body">
 *      <ul>
 *        <li>
 *          <span>Buy milk</span>
 *        </li>
 *      </ul>
 *    </div>
 *  </div>
 */
複製代碼
  • Container(List(ListItem(TodoItem))) 這裏咱們把TodoItem 數據傳給 ListItem, 而後結果做爲 List 的參數, 返回的結果又做爲 Container的參數

若是用 compose 函數,過程以下

R.compose(Container, List)(<li>Hello World!</li>);

/** * <div className="panel panel-default"> * <div className="panel-body"> * <ul> * <li>Hello World!</li> * </ul> * </div> * </div> */

const ContainerWithList = R.compose(Container, List);
R.compose(ContainerWithList, ListItem)({id: 123, text: 'Buy milk'});

/** * <div className="panel panel-default"> * <div className="panel-body"> * <ul> * <li> * <span>Buy milk</span> * </li> * </ul> * </div> * </div> */

const TodoItem = {
  id: 123,
  text: 'Buy milk'
};
const TodoList = R.compose(Container, List, ListItem);
TodoList(TodoItem);

/** * <div className="panel panel-default"> * <div className="panel-body"> * <ul> * <li> * <span>Buy milk</span> * </li> * </ul> * </div> * </div> */

複製代碼
  • const TodoList = R.compose(Container, List, ListItem)

列表的工廠函數,TodoList 組件能夠看做爲Container,List和 ListItem 的組合 如今 還只能接受一個參數, 須要能夠接受一個數組

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);

/** * <div className="panel panel-default"> * <div className="panel-body"> * <ul> * <li> * <span>One</span> * </li> * <li> * <span>Two</span> * </li> * <li> * <span>Three</span> * </li> * </ul> * </div> * </div> */
複製代碼
  • mapTodos 能夠有更簡單的模式
//This
return todos.map(function(todo) {
  return ListItem(todo);
});

//Is the same as
return todos.map(ListItem);

//So the result would be
const mapTodos = function(todos) {
  return todos.map(ListItem);
};

//The same using Ramda
const mapTodos = function(todos) {
  return R.map(ListItem, todos);
};

//Now remember two things from Ramda docs:
// - Ramda functions are automatically curried
// - The parameters to Ramda functions are arranged to make it convenient for currying.
// The data to be operated on is generally supplied last.
//So:
const mapTodos = R.map(ListItem);

//At this point mapTodos variable is rendudant, we don't need it anymore:
const TodoList = R.compose(Container, List, R.map(ListItem));
複製代碼
  • const mapTodos = R.map(ListItem); Ramda 函數式自動柯理化的,因此代碼是這樣的, 等待傳遞數據數組,返回的數組的形式是
  • {data.item}
  • 組成的數組

完整的 TodoList 的代碼就是

import React from 'React';
import R from 'ramda';

const Container = children => (<div className="panel panel-default"> <div className="panel-body"> {children} </div> </div>);

const List = children => (<ul> {children} </ul>);

const ListItem = ({ id, text }) => (<li key={id}> <span>{text}</span> </li>);

const TodoList = R.compose(Container, List, R.map(ListItem));

export default TodoList;
複製代碼

工廠配置好了,就等數據了

  • 模擬一下 appState 的 todo 數據
let appState = {
  secondsElapsed: 0,
  todos: [
    {id: 1, text: 'Buy milk'},
    {id: 2, text: 'Go running'},
    {id: 3, text: 'Rest'}
  ]
};
複製代碼
  • 在 App 組件中添加 TodoList 組件做爲子組件
import TodoList from './todo-list';
const App = appState => (<div className="container">
  <h1>App name</h1>
  <Timer secondsElapsed={appState.secondsElapsed} />
  <TodoList todos={appState.todos} />
</div>);
複製代碼

TodoList組件期待的參數是一個todos數組,

<TodoList todos={appState.todos} />
//const TodoList = R.compose(Container, List, R.map(ListItem))
複製代碼

React stateless component是做爲函數的,因此咱們也能夠傳遞參數

TodoList({todos: appState.todos});
複製代碼

最好是傳遞單個參數,因此這種狀況,再改進一下

const TodoList = R.compose(Container, List, R.map(ListItem), R.prop('todos'));
複製代碼

調用就直接改成:

TodoList(appState)
複製代碼

結束

相關文章
相關標籤/搜索