96 | {
97 | viewItems.map(item => {
98 | const isActive = item.id === activeId;
99 | return
100 | {
101 | typeof item.view === 'function' ?
102 | : item.view
103 | }
104 |
105 | })
106 | }
107 |
108 | )
109 | }
110 |
111 | refresh(props) {
112 | const { viewId, view, cacheTime } = props;
113 | const { activeId, viewItems } = this.state;
114 |
115 | const newViewItems = viewItems.slice();
116 |
117 | if (!viewId) {
118 | // set cache timer for switch-out item
119 | const preViewItem = newViewItems.find(item => item.id === activeId);
120 | this.setCacheTimer(preViewItem);
121 | }
122 | else {
123 | const viewItem = newViewItems.find(item => item.id === viewId);
124 | if (viewItem) {
125 | // update item
126 | viewItem.view = view;
127 | viewItem.cacheTime = cacheTime;
128 | viewItem.lastActivatedAt = new Date;
129 |
130 | if (viewId !== activeId) {
131 | // remove cache timer for switch-in item
132 | clearTimeout(viewItem.cacheTimer);
133 |
134 | // set cache timer for switch-out item
135 | const preViewItem = newViewItems.find(item => item.id === activeId);
136 | this.setCacheTimer(preViewItem)
137 | }
138 | }
139 | else {
140 | // create new item
141 | const newViewItem = {
142 | id: viewId,
143 | view: view,
144 | cacheTime: cacheTime,
145 | lastActivatedAt: new Date,
146 | };
147 | newViewItems.push(newViewItem);
148 |
149 | // set cache timer for previous item
150 | const preViewItem = newViewItems.find(item => item.id === activeId);
151 | this.setCacheTimer(preViewItem);
152 |
153 | // remove oldest surplus item
154 | if (newViewItems.length > this.getCacheLimit()) {
155 | const oldestItem = newViewItems.reduce(
156 | (min, cur) => cur.lastActivatedAt < min.lastActivatedAt ? cur : min,
157 | newViewItems[ 0 ]
158 | );
159 | if (oldestItem) {
160 | clearTimeout(oldestItem.cacheTimer);
161 | newViewItems.splice(newViewItems.findIndex(item => item === oldestItem), 1);
162 | }
163 | }
164 | }
165 | }
166 |
167 | // update state
168 | this.setState({
169 | activeId: viewId,
170 | viewItems: newViewItems,
171 | })
172 | }
173 |
174 | expireItem(id) {
175 | const { viewItems } = this.state;
176 | const viewItem = viewItems.find(item => item.id === id);
177 | if (viewItem) {
178 | clearTimeout(viewItem.cacheTimer);
179 | const newViewItems = viewItems.filter(item => item !== viewItem);
180 | this.setState({ viewItems: newViewItems });
181 | }
182 | }
183 |
184 | setCacheTimer(item) {
185 | if (item) {
186 | clearTimeout(item.cacheTimer);
187 | const cacheTime = this.getCacheTime(item);
188 | item.cacheTimer = setTimeout(() => this.expireItem(item.id), cacheTime);
189 | }
190 | }
191 |
192 | getCacheTime(item) {
193 | if (item.cacheTime !== undefined) return item.cacheTime;
194 | else return Cache.options.cacheTime;
195 | }
196 |
197 | getCacheLimit() {
198 | return Cache.options.cacheLimit;
199 | }
200 | }
201 |
202 | // optimize re-render
203 | class View extends React.Component {
204 | static propTypes = {
205 | args: PropTypes.object,
206 | view: PropTypes.func.isRequired,
207 | };
208 |
209 | shouldComponentUpdate(nextProps) {
210 | return !deepEqual(this.props, nextProps);
211 | }
212 |
213 | render() {
214 | const { args, view } = this.props;
215 | return view(args);
216 | }
217 | }
218 |
219 | return Cache;
220 | }
221 |
--------------------------------------------------------------------------------
/src/lib.js:
--------------------------------------------------------------------------------
1 | export function deepEqual(a, b) {
2 | if (!isObject(a) || !isObject(b)) return a === b;
3 | else return deepEqualInner(a, b) && deepEqualInner(b, a);
4 | }
5 |
6 | function deepEqualInner(a, b) {
7 | for (let key in a) {
8 | if (!deepEqual(a[ key ], b[ key ])) return false;
9 | }
10 | return true;
11 | }
12 |
13 | function isObject(x) {
14 | return typeof x === 'object' && x !== null;
15 | }
16 |
17 | export function deepAssign(base, extension) {
18 | if (!isObject(base)) base = {};
19 | if (!isObject(extension)) extension = {};
20 |
21 | for (let key in extension) {
22 | if (!isObject(extension[ key ])) base[ key ] = extension[ key ];
23 | else base[ key ] = deepAssign(base[ key ], extension[ key ]);
24 | }
25 |
26 | return base;
27 | }
28 |
29 | export function getIds(items) {
30 | return items.map(item => item.id);
31 | }
32 |
33 | export function difference(a, b) {
34 | const result = [];
35 | for (let value of a) {
36 | if (b.indexOf(value) < 0) result.push(value);
37 | }
38 | return result;
39 | }
--------------------------------------------------------------------------------
/test/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react", "es2015", "stage-0"]
3 | }
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |