- Simplest Redux: Just
state
andactions
, clear than ever before. - Two API totally:
createStore
andwithStore
, no more annoying concepts. - Async import model: Fully code splitting support for models.
- Auto
loading
state: Send request, and loading state is ready to use.
yarn add retalk
or
npm install retalk
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider, connect } from 'react-redux';
import { createStore, withStore } from 'retalk';
// 1. Model
const counter = {
state: {
count: 0,
},
actions: {
increment() {
const { count } = this.state;
this.setState({ count: count + 1 });
},
async incrementAsync() {
await new Promise((resolve) => setTimeout(resolve, 1000));
this.increment();
},
},
};
// 2. View
const Counter = connect(...withStore('counter'))(
({ count, increment, incrementAsync, loading }) => (
<div>
{count}
<button onClick={increment}>+</button>
<button onClick={incrementAsync}>+ Async{loading.incrementAsync && '...'}</button>
</div>
),
);
// 3. Store
const store = createStore({ counter });
const App = () => (
<Provider store={store}>
<Counter />
</Provider>
);
ReactDOM.render(<App />, document.getElementById('root'));
createStore(models[, options])
const store = createStore({ modelA, modelB }, { useDevTools: false, plugins: [logger] });
type: boolean
, default: true
. Enable Redux DevTools, make sure the extension's version >= v2.15.3 and not v2.16.0.
type: array
, default: []
. Add one middleware as an item to this array, passed to applyMiddleware
.
withStore(...modelNames)
const DemoConnected = connect(...withStore('modelA', 'modelB'))(Demo);
Use withStore
to eject all state and actions of a model to a component's props, you can eject more than one model.
withStore
must be passed in rest parameters syntax to connect()
.
actions: {
someAction() {
// What's in an action's `this` context?
// this.state -> Get state
// this.setState() -> Set state
// this.someOtherAction() -> Call actions
// this.someModel.state -> Get another model's state
// this.someModel.someAction() -> Call another model's actions
},
async someAsyncAction() {
// Automatically `loading.someAsyncAction` can be use
}
}
Use createStore
to initalize the store, then use libraries like loadable-components to dynamic import both the component and model.
Then use store.addModel(name, model)
to eject the async imported model to store.
Here is a loadable-components example:
import React from 'react';
import loadable from 'loadable-components';
const AsyncCounter = loadable(async (store) => {
const [{ default: Counter }, { default: model }] = await Promise.all([
import('./counter/index.jsx'),
import('./counter/model'),
]);
store.addModel('counter', model); // Key to import async model
return (props) => <Counter {...props} />;
});
Use mapStateToProps
and mapDispatchToProps
when need some customization, without using withStore
.
const mapState = ({ counter: { count } }) => ({
count,
});
const mapActions = ({ counter: { increment, incrementAsync } }) => ({
increment,
incrementAsync,
});
// First parameter to `mapDispatchToProps` is `dispatch`.
// `dispatch` is a function, but in `mapActions` above, we treat it like an object.
// Retalk did some tricks here, it's the `dispatch` function, but bound models on it.
export default connect(
mapState,
mapActions,
)(Counter);
For example change index.js
to:
if (module.hot) {
module.hot.accept('./App', () => {
render();
});
}
Then Provider
must inside the App
component:
const App = () => (
<Provider store={store}>
<Counter />
</Provider>
);
If want to keep the store, change store.js
to:
if (!window.store) {
window.store = createStore({ ... });
}
export default window.store;
Retalk uses Proxy
, if old browsers not support, please try proxy-polyfill.