add gs
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
local Roact = require(script.Parent.Parent.Roact)
|
||||
local storeKey = require(script.Parent.storeKey)
|
||||
local shallowEqual = require(script.Parent.shallowEqual)
|
||||
local join = require(script.Parent.join)
|
||||
|
||||
--[[
|
||||
Formats a multi-line message with printf-style placeholders.
|
||||
]]
|
||||
local function formatMessage(lines, parameters)
|
||||
return table.concat(lines, "\n"):format(unpack(parameters or {}))
|
||||
end
|
||||
|
||||
local function noop()
|
||||
return nil
|
||||
end
|
||||
|
||||
--[[
|
||||
The stateUpdater accepts props when they update and computes the
|
||||
complete set of props that should be passed to the wrapped component.
|
||||
|
||||
Each connected component will have a stateUpdater created for it.
|
||||
|
||||
stateUpdater is put into the component's state in order for
|
||||
getDerivedStateFromProps to be able to access it. It is not mutated.
|
||||
]]
|
||||
local function makeStateUpdater(store)
|
||||
return function(nextProps, prevState, mappedStoreState)
|
||||
-- The caller can optionally provide mappedStoreState if it needed that
|
||||
-- value beforehand. Doing so is purely an optimization.
|
||||
if mappedStoreState == nil then
|
||||
mappedStoreState = prevState.mapStateToProps(store:getState(), nextProps)
|
||||
end
|
||||
|
||||
local propsForChild = join(nextProps, mappedStoreState, prevState.mappedStoreDispatch)
|
||||
|
||||
return {
|
||||
mappedStoreState = mappedStoreState,
|
||||
propsForChild = propsForChild,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
mapStateToProps:
|
||||
(storeState, props) -> partialProps
|
||||
OR
|
||||
() -> (storeState, props) -> partialProps
|
||||
mapDispatchToProps: (dispatch) -> partialProps
|
||||
]]
|
||||
local function connect(mapStateToPropsOrThunk, mapDispatchToProps)
|
||||
local connectTrace = debug.traceback()
|
||||
|
||||
if mapStateToPropsOrThunk ~= nil then
|
||||
assert(typeof(mapStateToPropsOrThunk) == "function", "mapStateToProps must be a function or nil!")
|
||||
else
|
||||
mapStateToPropsOrThunk = noop
|
||||
end
|
||||
|
||||
if mapDispatchToProps ~= nil then
|
||||
assert(typeof(mapDispatchToProps) == "function", "mapDispatchToProps must be a function or nil!")
|
||||
else
|
||||
mapDispatchToProps = noop
|
||||
end
|
||||
|
||||
return function(innerComponent)
|
||||
if innerComponent == nil then
|
||||
local message = formatMessage({
|
||||
"connect returns a function that must be passed a component.",
|
||||
"Check the connection at:",
|
||||
"%s",
|
||||
}, {
|
||||
connectTrace,
|
||||
})
|
||||
|
||||
error(message, 2)
|
||||
end
|
||||
|
||||
local componentName = ("RoduxConnection(%s)"):format(tostring(innerComponent))
|
||||
|
||||
local Connection = Roact.Component:extend(componentName)
|
||||
|
||||
function Connection.getDerivedStateFromProps(nextProps, prevState)
|
||||
return prevState.stateUpdater(nextProps, prevState)
|
||||
end
|
||||
|
||||
function Connection:init()
|
||||
self.store = self._context[storeKey]
|
||||
|
||||
if self.store == nil then
|
||||
local message = formatMessage({
|
||||
"Cannot initialize Roact-Rodux connection without being a descendent of StoreProvider!",
|
||||
"Tried to wrap component %q",
|
||||
"Make sure there is a StoreProvider above this component in the tree.",
|
||||
}, {
|
||||
tostring(innerComponent),
|
||||
})
|
||||
|
||||
error(message)
|
||||
end
|
||||
|
||||
local storeState = self.store:getState()
|
||||
|
||||
local mapStateToProps = mapStateToPropsOrThunk
|
||||
local mappedStoreState = mapStateToProps(storeState, self.props)
|
||||
|
||||
-- mapStateToPropsOrThunk can return a function instead of a state
|
||||
-- value. In this variant, we keep that value as mapStateToProps
|
||||
-- instead of the original mapStateToProps. This matches react-redux
|
||||
-- and enables connectors to keep instance-level state.
|
||||
if typeof(mappedStoreState) == "function" then
|
||||
mapStateToProps = mappedStoreState
|
||||
mappedStoreState = mapStateToProps(storeState, self.props)
|
||||
end
|
||||
|
||||
if mappedStoreState ~= nil and typeof(mappedStoreState) ~= "table" then
|
||||
local message = formatMessage({
|
||||
"mapStateToProps must either return a table, or return another function that returns a table.",
|
||||
"Instead, it returned %q, which is of type %s.",
|
||||
}, {
|
||||
tostring(mappedStoreState),
|
||||
typeof(mappedStoreState),
|
||||
})
|
||||
|
||||
error(message)
|
||||
end
|
||||
|
||||
local mappedStoreDispatch = mapDispatchToProps(function(...)
|
||||
return self.store:dispatch(...)
|
||||
end)
|
||||
|
||||
local stateUpdater = makeStateUpdater(self.store)
|
||||
|
||||
self.state = {
|
||||
-- Combines props, mappedStoreDispatch, and the result of
|
||||
-- mapStateToProps into propsForChild. Stored in state so that
|
||||
-- getDerivedStateFromProps can access it.
|
||||
stateUpdater = stateUpdater,
|
||||
|
||||
-- Used by the store changed connection and stateUpdater to
|
||||
-- construct propsForChild.
|
||||
mapStateToProps = mapStateToProps,
|
||||
|
||||
-- Used by stateUpdater to construct propsForChild.
|
||||
mappedStoreDispatch = mappedStoreDispatch,
|
||||
|
||||
-- Passed directly into the component that Connection is
|
||||
-- wrapping.
|
||||
propsForChild = nil,
|
||||
}
|
||||
|
||||
self.state.propsForChild = stateUpdater(self.props, self.state, mappedStoreState)
|
||||
end
|
||||
|
||||
function Connection:didMount()
|
||||
self.storeChangedConnection = self.store.changed:connect(function(storeState)
|
||||
self:setState(function(prevState, props)
|
||||
local mappedStoreState = prevState.mapStateToProps(storeState, props)
|
||||
|
||||
-- We run this check here so that we only check shallow
|
||||
-- equality with the result of mapStateToProps, and not the
|
||||
-- other props that could be passed through the connector.
|
||||
if shallowEqual(mappedStoreState, prevState.mappedStoreState) then
|
||||
return nil
|
||||
end
|
||||
|
||||
return prevState.stateUpdater(props, prevState, mappedStoreState)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
function Connection:willUnmount()
|
||||
self.storeChangedConnection:disconnect()
|
||||
end
|
||||
|
||||
function Connection:render()
|
||||
return Roact.createElement(innerComponent, self.state.propsForChild)
|
||||
end
|
||||
|
||||
return Connection
|
||||
end
|
||||
end
|
||||
|
||||
return connect
|
||||
Reference in New Issue
Block a user