Skip to content

Commit

Permalink
FunctionComponent.Of extend func cache key with 'Props type name (#242)
Browse files Browse the repository at this point in the history
* FunctionComponent.Of prepared func cache key extended to include 'Props type name

* revert fsproj, write release notes

* move impl of Of into the cache class to reduce bundle size

---------

Co-authored-by: Nick Dunets <nick@chaldal.net>
  • Loading branch information
DunetsNM and Nick Dunets committed May 24, 2024
1 parent e217327 commit a35a41f
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 30 deletions.
75 changes: 45 additions & 30 deletions src/Fable.React/Fable.React.FunctionComponent.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,49 @@ open Fable.Core
open Fable.Core.JsInterop

#if FABLE_COMPILER
type internal Cache() =
type FunctionComponentPreparedRenderFunctionCache() =
static let cache =
let cache = JS.Constructors.Map.Create<string, obj>()
#if DEBUG
// Clear the cache when HMR is fired
Cache.OnHMR(fun () -> cache.clear())
FunctionComponentPreparedRenderFunctionCache.OnHMR(fun () -> cache.clear())
#endif
cache

static member GetOrAdd(key: string, valueFactory: string->'T): 'T =
if cache.has(key) then cache.get(key) :?> 'T
else let v = valueFactory key in cache.set(key, box v) |> ignore; v
static member GetOrAdd(
cacheKey: string,
displayName: string,
render: 'Props -> ReactElement,
memoizeWith: ('Props -> 'Props -> bool) option,
withKey: ('Props -> string) option,
[<CallerMemberName>] ?__callingMemberName: string) =
let prepareRenderFunction () =
render?displayName <- displayName
let elemType =
match memoizeWith with
| Some areEqual ->
#if DEBUG
// In development mode, force rerenders always when HMR is fired
let areEqual x y =
not FunctionComponentPreparedRenderFunctionCache.IsHMRApplied && areEqual x y
#endif
let memoElement = ReactElementType.memoWith areEqual render
memoElement?displayName <- "Memo(" + displayName + ")"
memoElement
| None -> ReactElementType.ofFunction render
fun props ->
let props =
match withKey with
| Some f -> props?key <- f props; props
| None -> props
ReactElementType.create elemType props []

if cache.has(cacheKey) then
cache.get(cacheKey) :?> ('Props -> ReactElement)
else
let v = prepareRenderFunction ()
cache.set(cacheKey, box v) |> ignore
v

[<Emit("""typeof module === 'object'
&& typeof module.hot === 'object'
Expand Down Expand Up @@ -57,7 +88,7 @@ type FunctionComponent =
/// and is displayed in React dev tools (use `displayName` to customize the name).
/// Uses React.memo if `memoizeWith` is specified (check `equalsButFunctions` and `memoEqualsButFunctions` helpers).
/// When you need a key to optimize collections in React you can use `withKey` argument or define a `key` field in the props object.
static member Of(render: 'Props->ReactElement,
static member inline Of(render: 'Props->ReactElement,
?displayName: string,
?memoizeWith: 'Props -> 'Props -> bool,
?withKey: 'Props -> string
Expand All @@ -68,32 +99,16 @@ type FunctionComponent =
#endif
): 'Props -> ReactElement =
#if FABLE_COMPILER
let prepareRenderFunction _ =
let displayName = defaultArg displayName __callingMemberName.Value
render?displayName <- displayName
let elemType =
match memoizeWith with
| Some areEqual ->
#if DEBUG
// In development mode, force rerenders always when HMR is fired
let areEqual x y =
not Cache.IsHMRApplied && areEqual x y
#endif
let memoElement = ReactElementType.memoWith areEqual render
memoElement?displayName <- "Memo(" + displayName + ")"
memoElement
| None -> ReactElementType.ofFunction render
fun props ->
let props =
match withKey with
| Some f -> props?key <- f props; props
| None -> props
ReactElementType.create elemType props []

// Cache the render function to prevent recreating the component every time when FunctionComponent.Of
// is called inside another function (including generic values: let MyCom<'T> = ...)
let cacheKey = __callingSourceFile.Value + "#L" + (string __callingSourceLine.Value)
Cache.GetOrAdd(cacheKey, prepareRenderFunction)
let cacheKey =
__callingSourceFile.Value +
"#L" + (string __callingSourceLine.Value) +
// direct caller can also be generic, need separate cached func per 'Props argument
";" + typeof<'Props>.FullName
let displayName = defaultArg displayName __callingMemberName.Value

FunctionComponentPreparedRenderFunctionCache.GetOrAdd (cacheKey, displayName, render, memoizeWith, withKey)
#else
let elemType = ReactElementType.ofFunction render
fun props ->
Expand Down
4 changes: 4 additions & 0 deletions src/Fable.React/RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### 9.4.0

- Fix FunctionComponent's render func caching for generic usages

### 9.3.0

- Add pointer events to `DOMAttr`
Expand Down

0 comments on commit a35a41f

Please sign in to comment.