From a1631bea7108c250ae7a284f8ada0eb2fa893b95 Mon Sep 17 00:00:00 2001 From: cpojer Date: Mon, 9 Mar 2015 18:11:45 -0700 Subject: [PATCH] Add shallowCompare module and use it in PureRenderMixin + tests --- .../ReactComponentWithPureRenderMixin.js | 5 +- .../ReactComponentWithPureRenderMixin-test.js | 145 ++++++++++++++++++ src/addons/shallowCompare.js | 27 ++++ src/browser/ReactWithAddons.js | 2 + 4 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 src/addons/__tests__/ReactComponentWithPureRenderMixin-test.js create mode 100644 src/addons/shallowCompare.js diff --git a/src/addons/ReactComponentWithPureRenderMixin.js b/src/addons/ReactComponentWithPureRenderMixin.js index fc367d7c685b2..05265e17c160a 100644 --- a/src/addons/ReactComponentWithPureRenderMixin.js +++ b/src/addons/ReactComponentWithPureRenderMixin.js @@ -11,7 +11,7 @@ 'use strict'; -var shallowEqual = require('shallowEqual'); +var shallowCompare = require('shallowCompare'); /** * If your React component's render function is "pure", e.g. it will render the @@ -39,8 +39,7 @@ var shallowEqual = require('shallowEqual'); */ var ReactComponentWithPureRenderMixin = { shouldComponentUpdate: function(nextProps, nextState) { - return !shallowEqual(this.props, nextProps) || - !shallowEqual(this.state, nextState); + return shallowCompare(this, nextProps, nextState); } }; diff --git a/src/addons/__tests__/ReactComponentWithPureRenderMixin-test.js b/src/addons/__tests__/ReactComponentWithPureRenderMixin-test.js new file mode 100644 index 0000000000000..c8db634d218f3 --- /dev/null +++ b/src/addons/__tests__/ReactComponentWithPureRenderMixin-test.js @@ -0,0 +1,145 @@ +/** + * Copyright 2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @emails react-core + */ + +'use strict'; + +var React; +var ReactComponentWithPureRenderMixin; +var ReactTestUtils; + +describe('ReactComponentWithPureRenderMixin', function() { + + beforeEach(function() { + React = require('React'); + ReactComponentWithPureRenderMixin = + require('ReactComponentWithPureRenderMixin'); + ReactTestUtils = require("../../ReactTestUtils"); + }); + + it('provides a default shouldComponentUpdate implementation', function() { + var renderCalls = 0; + class PlasticWrap extends React.Component { + constructor(props, context) { + super(props, context); + this.state = { + color: 'green' + }; + } + + render() { + return ( + + ); + } + } + + var Apple = React.createClass({ + mixins: [ReactComponentWithPureRenderMixin], + + getInitialState: function() { + return { + cut: false, + slices: 1, + } + }, + + cut: function() { + this.setState({ + cut: true, + slices: 10, + }); + }, + + eatSlice: function() { + this.setState({ + slices: this.state.slices - 1, + }); + }, + + render: function() { + renderCalls++; + return
; + } + }); + + var instance = ReactTestUtils.renderIntoDocument(); + expect(renderCalls).toBe(1); + + // Do not re-render based on props + instance.setState({color: 'green'}); + expect(renderCalls).toBe(1); + + // Re-render based on props + instance.setState({color: 'red'}); + expect(renderCalls).toBe(2); + + // Re-render base on state + instance.refs.apple.cut(); + expect(renderCalls).toBe(3); + + // No re-render based on state + instance.refs.apple.cut(); + expect(renderCalls).toBe(3); + + // Re-render based on state again + instance.refs.apple.eatSlice(); + expect(renderCalls).toBe(4); + }); + + it('does not do a deep comparison', function() { + function getInitialState() { + return { + foo: [1, 2, 3], + bar: {a: 4, b: 5, c: 6}, + }; + } + + var renderCalls = 0; + var initialSettings = getInitialState(); + + var Component = React.createClass({ + mixins: [ReactComponentWithPureRenderMixin], + + getInitialState: function() { + return initialSettings; + }, + + render: function() { + renderCalls++; + return
; + } + }); + + var instance = ReactTestUtils.renderIntoDocument(); + expect(renderCalls).toBe(1); + + // Do not re-render if state is equal + var settings = { + foo: initialSettings.foo, + bar: initialSettings.bar, + }; + instance.setState(settings); + expect(renderCalls).toBe(1); + + // Re-render because one field changed + initialSettings.foo = [1, 2, 3]; + instance.setState(initialSettings); + expect(renderCalls).toBe(2); + + // Re-render because the object changed + instance.setState(getInitialState()); + expect(renderCalls).toBe(3); + }); + +}); diff --git a/src/addons/shallowCompare.js b/src/addons/shallowCompare.js new file mode 100644 index 0000000000000..4f6b17cdc8839 --- /dev/null +++ b/src/addons/shallowCompare.js @@ -0,0 +1,27 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * +* @providesModule shallowCompare +*/ + +'use strict'; + +var shallowEqual = require('shallowEqual'); + +/** + * Does a shallow comparison for props and state. + * See ReactComponentWithPureRenderMixin + */ +function shallowCompare(instance, nextProps, nextState) { + return ( + !shallowEqual(instance.props, nextProps) || + !shallowEqual(instance.state, nextState) + ); +} + +module.exports = shallowCompare; diff --git a/src/browser/ReactWithAddons.js b/src/browser/ReactWithAddons.js index 6dfe9da083aba..e91c591419d9b 100644 --- a/src/browser/ReactWithAddons.js +++ b/src/browser/ReactWithAddons.js @@ -29,6 +29,7 @@ var ReactUpdates = require('ReactUpdates'); var cx = require('cx'); var cloneWithProps = require('cloneWithProps'); +var shallowCompare = require('shallowCompare'); var update = require('update'); React.addons = { @@ -41,6 +42,7 @@ React.addons = { classSet: cx, cloneWithProps: cloneWithProps, createFragment: ReactFragment.create, + shallowCompare: shallowCompare, update: update };