blainekasten / coflux Goto Github PK
View Code? Open in Web Editor NEWFlux at the Component Level
Flux at the Component Level
Technically this project isn't using any specific RxJS. Though the way things work, it feels like each component observes state when it changes. I'm wondering if observeState
would help describe what components are doing better, and sort of make a clearer seperation from redux.
wrap(Comp, {
observeState() {
return {
foo: 'bar.baz'
};
},
actions: {
...
},
});
thoughts?
I'm curious if we could just have the values live in the react class. Not sure how this would play with stateless components (which we're aiming to support).
Imagining this:
class
@coflux
class Foo extends Component {
render() {
}
mapStateToProps() {
}
static actions = {
foobar() { }
}
}
statelss
const actions = {};
@coflux(
mapStateToProps() {},
actions
)
function Foo() {
}
Their needs to be some reverse crawling to dictate if child components would need a shouldComponentUpdate
call.
The current issue is this. Assume all components wrap to different data.
wrap(A, { userId: 'user.id' });
wrap(B, { firstName: 'user.firstName' });
wrap(C, { userId: 'user.id' });
Given this, if it is rendered in the following structure:
A
/ \
B C
If user.id
was changed, A would re-render, B wouldn't, and C would.
BUT.
if user.firstName
changed. Nothing would get updated correctly.
That's wrong. We should find a way to track it's internal dependency in reverse up the tree. So given the above, at runtime it effectively internally changes to:
wrap(A, { userId: 'user.id', firstName: 'user.firstName' })
The idea here is that https://github.com/blainekasten/coflux/tree/master/docs#mapped-props-with-_ is an awkward API that could be auto-generated by a babel transform.
The _
is a perf gain because components sometimes can modify data they aren't rendering. For example a component with a delete all button isn't likely rendering anything based on state, but clicking it will empty an array.
e.g.,
function DeleteAll({actions}) {
return <button onClick={actions.deleteAll}>Delete All</button>;
}
wrap(DeleteAll, {
mapStateToProps() {
return {
emailList: 'app.user.emails';
};
},
actions: {
deleteAll({emailList}, next) {
next({emailList: null});
/*
* We need to know about emailList so we can modify it
* But we dont' need to re-render if the list changes,
* because we aren't rendering it.
*/
}
}
});
So we have this perf optimization (that could change, but for now it works) that a mapped prop prefixed with _
is marked as write-only. Meaning it doesn't read from it, and doesn't care if the value changes.
While this makes sense, it'd be best if we could have a babel transform, that could read the components render function, and determine what props are write-only and autoprefix the keys with _
.
In order to successfully make a flux library today, it needs all the bells and whistles. coflux
should ship with a good set of devtools to make debugging simpler.
Talking with @developerdizzle. We talked about middlewares and the general ideas. Here are some thoughts as a take away.
Legimiate cases for middleware are:
I think there are 2 ways we could approach this. actionExtender's or generic middleware.
This is how I would expect them to be implemented:
// happens at some top level
const history = [];
actionExtender('undoable', function(prevState, proposedState, componentName, actionName) {
// do something with the previous or proposed state
history.push(prevState);
});
function foo({actions}) {
return <input onClick={actions.handleClick.undoable} /> // undoable being the key here
}
wrap(foo, { actions: {
handleClick(state, next) {
// does something
next({foo: 'bar'});
}
}});
Pro's here are actions can opt into using a middleware. This technically isn't middleware but it might support all the use-cases we need.
This is true middleware, every action called also calls the middleware's.
// some top level file
middleware(function logger(prevState, proposedState, componentName, actionName) {
fetch('api/v2/log', {prevState, proposedState, componentName, actionName});
});
The implementation of this should probably live on Provider
. Something like this:
render(
<Provider middlewares={[logger, devTools]}>
...
</Provider>
)
MVP requires a property of store
on Provider
. I dream of a day where a Babel Transform can assume all of the store's structure for you so you don't have to map these things. By observing the relationship of components to state, I think it should be inferable.
This is an unknown, but if the state is not needed as a default argument, there has to be a way to set the default state. Initial thoughts are a callback of a sort.
<Provider defaultState={() => { foo: 'bar', x: true }} />
If we can probably infer the store shape, we can also then do a propType
type thing to make sure updatedState doesn't break the shape. For example:
If we know that state.x
is an array, and somebody's action tries to set it to a string, then the store API is broken.
// state.nicknames = ['joe', 'james', 'john'];
// action
updateNickname({nickname}, next) {
next({nickname: 'jacked up joe'});
});
This should throw an error because the state is inferred to be an array from either some default data, or through introspection of other components.
This is the biggest limitation i've found thus far:
Lists.
Due to implementation things, it's hard for a ListItem component to bind to it's index in the store. So they can't ever be wrap
ped. Is that actually a problem? Is it fine if the List itself is wrap
ped and just passes props the normal way? The hard part there is that the ListItem can't update state without passing actions created in the List.
Here is the only way to create a list right now:
function List({items, actions}) {
return (
<div>
{items.map(
(item, i) => <ListItem {...item} updateItem={actions.updateItem.bind(null, i)} />
}
</div>
);
}
wrap(List, {
mapStateToProps() {...},
actions: { updateItem() {...} },
});
function ListItem({details, updateItem}) {
return (
<div onClick={updateItem}>{details}</div>
);
}
This is how I dream we could make it:
function List({items, actions}) {
return (
<div>
{items.map(
(item, i) => <ListItem {...item} index={i} />
}
</div>
);
}
wrap(List, {
mapStateToProps() {
return { items: 'items' };
}
});
function ListItem({details, updateItem}) {
return (
<div onClick={updateItem}>{details}</div>
);
}
wrap(List, {
mapStateToProps(index) {
return {
details: `items[${index}].details`,
};
},
actions: { updateItem() {...} },
});
Looking for feedback on this library.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.