Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FunctionComponent.Of extend func cache key with 'Props type name #242

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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