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:
parent
ddd905ec26
commit
9ae8f7295d
@ -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>')
|
||||
})
|
||||
})
|
||||
|
@ -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>
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user