0
0
mirror of https://github.com/honojs/hono.git synced 2024-11-30 01:56:18 +01:00

feat(middleware/jsx): Introduce memo. (#309)

* feat(middleware/jsx): Introduce memo.

* fix(middleware/jsx): propsAreEqual is always defined.
This commit is contained in:
Taku Amano 2022-06-10 15:09:29 +09:00 committed by GitHub
parent ddd905ec26
commit 9ae8f7295d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 102 additions and 3 deletions

View File

@ -1,5 +1,5 @@
import { Hono } from '../../hono'
import { h, jsx } from '.'
import { h, jsx, memo } from '.'
describe('JSX middleware', () => {
const app = new Hono()
@ -97,3 +97,66 @@ describe('render to string', () => {
})
})
})
describe('memo', () => {
it('memoized', () => {
let counter = 0
const Header = memo(() => <title>Test Site {counter}</title>)
const Body = () => <span>{counter}</span>
let template = (
<html>
<head>
<Header />
</head>
<body>
<Body />
</body>
</html>
)
expect(template.toString()).toBe(
'<html><head><title>Test Site 0</title></head><body><span>0</span></body></html>'
)
counter++
template = (
<html>
<head>
<Header />
</head>
<body>
<Body />
</body>
</html>
)
expect(template.toString()).toBe(
'<html><head><title>Test Site 0</title></head><body><span>1</span></body></html>'
)
})
it('props are updated', () => {
const Body = memo(({ counter }: { counter: number }) => <span>{counter}</span>)
let template = <Body counter={0} />
expect(template.toString()).toBe('<span>0</span>')
template = <Body counter={1} />
expect(template.toString()).toBe('<span>1</span>')
})
it('custom propsAreEqual', () => {
const Body = memo(
({ counter }: { counter: number, refresh?: boolean }) => <span>{counter}</span>,
(_, nextProps) => typeof nextProps.refresh == 'undefined' ? true : !nextProps.refresh
)
let template = <Body counter={0} />
expect(template.toString()).toBe('<span>0</span>')
template = <Body counter={1} />
expect(template.toString()).toBe('<span>0</span>')
template = <Body counter={2} refresh={true} />
expect(template.toString()).toBe('<span>2</span>')
})
})

View File

@ -46,8 +46,7 @@ export const h = (
escapedString.isEscaped = true
children = [escapedString]
continue
}
else if (v === null || v === undefined) {
} else if (v === null || v === undefined) {
continue
}
@ -75,3 +74,40 @@ export const h = (
return escapedString
}
type FC<T = Record<string, any>> = (props: T) => EscapedString
const shallowEqual = (a: Record<string, any>, b: Record<string, any>): boolean => {
if (a === b) {
return true
}
const aKeys = Object.keys(a)
const bKeys = Object.keys(b)
if (aKeys.length !== bKeys.length) {
return false
}
for (let i = 0, len = aKeys.length; i < len; i++) {
if (a[aKeys[i]] !== b[aKeys[i]]) {
return false
}
}
return true
}
export const memo = <T>(
component: FC<T>,
propsAreEqual: (prevProps: Readonly<T>, nextProps: Readonly<T>) => boolean = shallowEqual
): FC<T> => {
let computed = undefined
let prevProps: T | undefined = undefined
return ((props: T): EscapedString => {
if (prevProps && !propsAreEqual(prevProps, props)) {
computed = undefined
}
prevProps = props
return (computed ||= component(props))
}) as FC<T>
}