From 7015a579edebd0fe2f306936a3fd958438d80f1c Mon Sep 17 00:00:00 2001 From: Nathan Wenneker Date: Thu, 5 Nov 2015 18:17:59 -0700 Subject: [PATCH] Memoize prop transformation functions and specify their arguments Meant to be similar to reselect's `createSelector`, but instead of reaching into redux `state`, it reaches into the component's `this`. --- components/AddTodo.js | 2 +- containers/App.js | 50 ++++++++++++++++++++++++++++++------------- memoize.js | 29 +++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 16 deletions(-) create mode 100644 memoize.js diff --git a/components/AddTodo.js b/components/AddTodo.js index c3b6140..b91ea0f 100644 --- a/components/AddTodo.js +++ b/components/AddTodo.js @@ -4,7 +4,7 @@ export default class AddTodo extends Component { render() { return (
- + Add: diff --git a/containers/App.js b/containers/App.js index 916c3a1..599defd 100644 --- a/containers/App.js +++ b/containers/App.js @@ -4,20 +4,51 @@ import { addTodo, completeTodo, setVisibilityFilter, changeTheme, VisibilityFilt import AddTodo from '../components/AddTodo' import TodoList from '../components/TodoList' import Footer from '../components/Footer' +import { memoize, createMemoizedFunction } from '../memoize' + +function selectTodos(todos, filter) { + console.log("Recalculating selectTodos"); + switch (filter) { + case VisibilityFilters.SHOW_ALL: + return todos + case VisibilityFilters.SHOW_COMPLETED: + return todos.filter(todo => todo.completed) + case VisibilityFilters.SHOW_ACTIVE: + return todos.filter(todo => !todo.completed) + } +} + +function selectMatchingTodos(todos, search) { + console.log("Recalculating matchingTodos"); + return todos.filter((todo) => { return todo.text.search(search) >= 0; }); +} class App extends Component { + constructor(props, context) { + super(props, context); + this.state = { search: '' }; + } + + visibleTodos = createMemoizedFunction(() => [this.props.todos, this.props.visibilityFilter], selectTodos); + matchingTodos = createMemoizedFunction(() => [this.visibleTodos(), this.state.search], selectMatchingTodos); + + updateSearch = function(e) { + this.setState({ search: e.target.value }); + } + render() { console.log(this.props); // Injected by connect() call: - const { dispatch, visibleTodos, visibilityFilter, currentTheme } = this.props + const { dispatch, visibilityFilter, currentTheme } = this.props return (
+ Search:
dispatch(addTodo(text)) } /> dispatch(completeTodo(index)) } /> @@ -33,7 +64,7 @@ class App extends Component { } App.propTypes = { - visibleTodos: PropTypes.arrayOf(PropTypes.shape({ + todos: PropTypes.arrayOf(PropTypes.shape({ text: PropTypes.string.isRequired, completed: PropTypes.bool.isRequired })), @@ -44,22 +75,11 @@ App.propTypes = { ]).isRequired } -function selectTodos(todos, filter) { - switch (filter) { - case VisibilityFilters.SHOW_ALL: - return todos - case VisibilityFilters.SHOW_COMPLETED: - return todos.filter(todo => todo.completed) - case VisibilityFilters.SHOW_ACTIVE: - return todos.filter(todo => !todo.completed) - } -} - // Which props do we want to inject, given the global state? // Note: use https://github.com/faassen/reselect for better performance. function select(state) { return { - visibleTodos: selectTodos(state.todos, state.visibilityFilter), + todos: state.todos, visibilityFilter: state.visibilityFilter, currentTheme: state.currentTheme } diff --git a/memoize.js b/memoize.js new file mode 100644 index 0000000..d3121d3 --- /dev/null +++ b/memoize.js @@ -0,0 +1,29 @@ +// This memoizes a function by remembering its last args and its last result +// and returning the last result if the args are the same as the last call. +export function memoize(func) { + var lastArgs = null; + var lastResult; + + function argsDifferent(args) { + return lastArgs === null || + lastArgs.length != args.length || + args.some((arg, idx) => { return arg !== lastArgs[idx] }); + } + + return function(...args) { + if(argsDifferent(args)) { + lastArgs = args; + lastResult = func(...args); + } + return lastResult + } +} + +// This memoizes `func` and returns a function that calls +// the memoized `func` with the arguments returned by `argFunc` +export function createMemoizedFunction(argFunc, func) { + var memoized = memoize(func); + return function() { + return memoized(...argFunc()); + } +}