immerjs / immer

Create the next immutable state by mutating the current one

Home Page:https://immerjs.github.io/immer/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Efficiency of returning from produce

oliversturm opened this issue Β· comments

πŸ™‹β€β™‚ Question

This documentation page includes the following code sample:

case "adduser-3":
  // OK: returning a new state. But, unnecessary complex and expensive
  return {
    userCount: draft.userCount + 1,
    users: [...draft.users, action.payload]
  }

I'm interested in this syntax because I personally like it much better than the imperative approach preferred by Immer. I don't want to begin a discussion about the pros and cons of imperative vs functional syntax variants, but I'd like to know why (or challenge the understanding that) this syntax is unnecessarily complex and expensive.

From a pure JS point of view, I don't see how Immer could possibly apply a recipe based on imperative syntax with considerably greater efficiency. I agree that the sample syntax is a few characters longer, so we can count that as "complex" if you like - but "expensive"? What's the reason for this assessment?

I just had one thought - perhaps the point is that this approach bypasses the mechanism provided by Immer, so that extra effort (creating a proxy object, etc) is rendered pointless? And this is what is regarded "expensive"? Just a thought - trying to understand what the writer of that sentence had on their mind.

I was curious and decided to investigate this further. My result is: the functional syntax is more efficient than the Immer-preferred imperative syntax, not less. This is compatible with my expectations, so I'll just leave it at that.

I added this test code to the existing __performance_tests__/add-data.js:

const baseState2 = {
	count: 0,
	data: []
}
const frozenBaseState2 = deepFreeze(cloneDeep(baseState2))
const oneObject = {
	thing: "some thing"
}

measure("immer with imperative changes", () => {
	setUseProxies(true)
	setAutoFreeze(true)
	for (let i = 0; i < MAX; i++)
		produce(frozenBaseState2, draft => {
			draft.count++
			draft.data.push(oneObject)
		})
})

measure("immer with functional changes", () => {
	setUseProxies(true)
	setAutoFreeze(true)
	for (let i = 0; i < MAX; i++)
		produce(frozenBaseState2, draft => ({
			count: draft.count + 1,
			data: draft.data.concat(oneObject)
		}))
})

The results are these, on my machine:

immer with imperative changes: 88ms
immer with functional changes: 67ms