mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] store: getters
This commit is contained in:
committed by
Géry Debongnie
parent
d928927da8
commit
147f6fced7
+13
-11
@@ -83,7 +83,7 @@ object, or modifying an array with the `arr[i] = newValue` syntax). See the
|
|||||||
### Mutations
|
### Mutations
|
||||||
|
|
||||||
Mutations are the only way to modify the state. Changing the state outside a
|
Mutations are the only way to modify the state. Changing the state outside a
|
||||||
mutation is not allowed (and should throw an error). Mutations as synchronous.
|
mutation is not allowed (and should throw an error). Mutations are synchronous.
|
||||||
|
|
||||||
### Actions
|
### Actions
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ in an action.
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
const actions = {
|
const actions = {
|
||||||
login({commit}) {
|
async login({commit}) {
|
||||||
commit('setLoginState', 'pending');
|
commit('setLoginState', 'pending');
|
||||||
try {
|
try {
|
||||||
const loginInfo = await doSomeRPC('/login/', 'someinfo');
|
const loginInfo = await doSomeRPC('/login/', 'someinfo');
|
||||||
@@ -128,18 +128,20 @@ transform the data contained in the store.
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
const getters = {
|
const getters = {
|
||||||
getPost({state}) {
|
getPost({state}, id) {
|
||||||
return function (id) {
|
const post = state.posts.find(p => p.id === id);
|
||||||
const post = state.posts.find(p => p.id === id);
|
const author = state.authors.find(a => a.id = post.id);
|
||||||
const author = state.authors.find(a => a.id = post.id);
|
return {
|
||||||
return {
|
id,
|
||||||
id,
|
author,
|
||||||
author,
|
content: post.content
|
||||||
content: post.content
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// somewhere else
|
||||||
|
const post = store.getters.getPost(id);
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Connecting a component
|
### Connecting a component
|
||||||
|
|||||||
+34
-8
@@ -21,13 +21,15 @@ import { Observer } from "./observer";
|
|||||||
// Store Definition
|
// Store Definition
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
type Mutation = ({state, commit, set}, payload: any) => void;
|
type Mutation = ({ state, commit, set, getters }, payload: any) => void;
|
||||||
type Action = ({commit, state, dispatch, env}, payload: any) => void;
|
type Action = ({ commit, state, dispatch, env, getters }, payload: any) => void;
|
||||||
|
type Getter = ({ state, getters }, payload) => any;
|
||||||
|
|
||||||
interface StoreConfig {
|
interface StoreConfig {
|
||||||
env?: Env;
|
env?: Env;
|
||||||
state?: any;
|
state?: any;
|
||||||
actions?: {[name: string]: Action};
|
actions?: { [name: string]: Action };
|
||||||
|
getters?: { [name: string]: Getter };
|
||||||
mutations?: { [name: string]: Mutation };
|
mutations?: { [name: string]: Mutation };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +47,7 @@ export class Store extends EventBus {
|
|||||||
env: any;
|
env: any;
|
||||||
observer: Observer;
|
observer: Observer;
|
||||||
set: any;
|
set: any;
|
||||||
|
getters: { [name: string]: Getter };
|
||||||
|
|
||||||
constructor(config: StoreConfig, options: StoreOption = {}) {
|
constructor(config: StoreConfig, options: StoreOption = {}) {
|
||||||
super();
|
super();
|
||||||
@@ -57,11 +60,23 @@ export class Store extends EventBus {
|
|||||||
this.observer.notifyCB = this.trigger.bind(this, "update");
|
this.observer.notifyCB = this.trigger.bind(this, "update");
|
||||||
this.observer.allowMutations = false;
|
this.observer.allowMutations = false;
|
||||||
this.observer.observe(this.state);
|
this.observer.observe(this.state);
|
||||||
|
this.getters = {};
|
||||||
|
|
||||||
if (this.debug) {
|
if (this.debug) {
|
||||||
this.history.push({ state: this.state });
|
this.history.push({ state: this.state });
|
||||||
}
|
}
|
||||||
this.set = this.observer.set.bind(this.observer);
|
this.set = this.observer.set.bind(this.observer);
|
||||||
|
|
||||||
|
for (let entry of Object.entries(config.getters || {})) {
|
||||||
|
const name: string = entry[0];
|
||||||
|
const func: (...any) => any = entry[1];
|
||||||
|
Object.defineProperty(this.getters, name, {
|
||||||
|
get: func.bind(this, {
|
||||||
|
state: this.state,
|
||||||
|
getters: this.getters
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(action: string, payload?: any): Promise<void> | void {
|
dispatch(action: string, payload?: any): Promise<void> | void {
|
||||||
@@ -73,7 +88,8 @@ export class Store extends EventBus {
|
|||||||
commit: this.commit.bind(this),
|
commit: this.commit.bind(this),
|
||||||
dispatch: this.dispatch.bind(this),
|
dispatch: this.dispatch.bind(this),
|
||||||
env: this.env,
|
env: this.env,
|
||||||
state: this.state
|
state: this.state,
|
||||||
|
getters: this.getters
|
||||||
},
|
},
|
||||||
payload
|
payload
|
||||||
);
|
);
|
||||||
@@ -97,7 +113,8 @@ export class Store extends EventBus {
|
|||||||
{
|
{
|
||||||
commit: this.commit.bind(this),
|
commit: this.commit.bind(this),
|
||||||
state: this.state,
|
state: this.state,
|
||||||
set: this.set
|
set: this.set,
|
||||||
|
getters: this.getters
|
||||||
},
|
},
|
||||||
payload
|
payload
|
||||||
);
|
);
|
||||||
@@ -174,7 +191,11 @@ export function connect(mapStateToProps, options: any = {}) {
|
|||||||
constructor(parent, props?: any) {
|
constructor(parent, props?: any) {
|
||||||
const env = parent instanceof Component ? parent.env : parent;
|
const env = parent instanceof Component ? parent.env : parent;
|
||||||
const ownProps = Object.assign({}, props || {});
|
const ownProps = Object.assign({}, props || {});
|
||||||
const storeProps = mapStateToProps(env.store.state, ownProps);
|
const storeProps = mapStateToProps(
|
||||||
|
env.store.state,
|
||||||
|
ownProps,
|
||||||
|
env.store.getters
|
||||||
|
);
|
||||||
const mergedProps = Object.assign({}, props || {}, storeProps);
|
const mergedProps = Object.assign({}, props || {}, storeProps);
|
||||||
super(parent, mergedProps);
|
super(parent, mergedProps);
|
||||||
(<any>this.__owl__).ownProps = ownProps;
|
(<any>this.__owl__).ownProps = ownProps;
|
||||||
@@ -207,7 +228,11 @@ export function connect(mapStateToProps, options: any = {}) {
|
|||||||
|
|
||||||
_checkUpdate() {
|
_checkUpdate() {
|
||||||
const ownProps = (<any>this.__owl__).ownProps;
|
const ownProps = (<any>this.__owl__).ownProps;
|
||||||
const storeProps = mapStateToProps(this.env.store.state, ownProps);
|
const storeProps = mapStateToProps(
|
||||||
|
this.env.store.state,
|
||||||
|
ownProps,
|
||||||
|
this.env.store.getters
|
||||||
|
);
|
||||||
const options: any = {
|
const options: any = {
|
||||||
currentStoreProps: (<any>this.__owl__).currentStoreProps
|
currentStoreProps: (<any>this.__owl__).currentStoreProps
|
||||||
};
|
};
|
||||||
@@ -234,7 +259,8 @@ export function connect(mapStateToProps, options: any = {}) {
|
|||||||
if ((<any>this.__owl__).ownProps !== nextProps) {
|
if ((<any>this.__owl__).ownProps !== nextProps) {
|
||||||
(<any>this.__owl__).currentStoreProps = mapStateToProps(
|
(<any>this.__owl__).currentStoreProps = mapStateToProps(
|
||||||
this.env.store.state,
|
this.env.store.state,
|
||||||
nextProps
|
nextProps,
|
||||||
|
this.env.store.getters
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
(<any>this.__owl__).ownProps = nextProps;
|
(<any>this.__owl__).ownProps = nextProps;
|
||||||
|
|||||||
@@ -169,6 +169,131 @@ describe("basic use", () => {
|
|||||||
expect(updateCounter).toBe(1);
|
expect(updateCounter).toBe(1);
|
||||||
expect(store.state).toEqual({ bertinchamps: "brune", chouffe: "blonde" });
|
expect(store.state).toEqual({ bertinchamps: "brune", chouffe: "blonde" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("can have getters from store", async () => {
|
||||||
|
const state = {
|
||||||
|
beers: {
|
||||||
|
1: {
|
||||||
|
id: 1,
|
||||||
|
name: "bertinchamps",
|
||||||
|
tasterID: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tasters: {
|
||||||
|
1: {
|
||||||
|
id: 1,
|
||||||
|
name: 'aaron',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const getters = {
|
||||||
|
beerTasterName({ state }) {
|
||||||
|
return beerID => {
|
||||||
|
return state.tasters[state.beers[beerID].tasterID].name;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bestBeerName({ state }) {
|
||||||
|
return state.beers[1].name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const store = new Store({ state, mutations: {}, actions: {}, getters });
|
||||||
|
expect(store.getters).toBeDefined();
|
||||||
|
expect((<any>store.getters).bestBeerName).toBe("bertinchamps");
|
||||||
|
expect((<any>store.getters).beerTasterName(1)).toBe("aaron");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getters given to actions", async () => {
|
||||||
|
expect.assertions(3);
|
||||||
|
const state = {
|
||||||
|
beers: {
|
||||||
|
1: {
|
||||||
|
id: 1,
|
||||||
|
name: "bertinchamps",
|
||||||
|
tasterID: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tasters: {
|
||||||
|
1: {
|
||||||
|
id: 1,
|
||||||
|
name: 'aaron',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const getters = {
|
||||||
|
beerTasterName({ state }) {
|
||||||
|
return beerID => {
|
||||||
|
return state.tasters[state.beers[beerID].tasterID].name;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bestBeerName({ state }) {
|
||||||
|
return state.beers[1].name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const actions = {
|
||||||
|
action({ getters }) {
|
||||||
|
expect(getters).toBeDefined();
|
||||||
|
expect(getters.bestBeerName).toBe("bertinchamps");
|
||||||
|
expect(getters.beerTasterName(1)).toBe("aaron");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const store = new Store({ state, mutations: {}, actions, getters });
|
||||||
|
store.dispatch("action");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getters given to mutations", async () => {
|
||||||
|
expect.assertions(3);
|
||||||
|
const state = {
|
||||||
|
beers: {
|
||||||
|
1: {
|
||||||
|
id: 1,
|
||||||
|
name: "bertinchamps",
|
||||||
|
tasterID: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tasters: {
|
||||||
|
1: {
|
||||||
|
id: 1,
|
||||||
|
name: 'aaron',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const getters = {
|
||||||
|
beerTasterName({ state }) {
|
||||||
|
return beerID => {
|
||||||
|
return state.tasters[state.beers[beerID].tasterID].name;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bestBeerName({ state }) {
|
||||||
|
return state.beers[1].name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const mutations = {
|
||||||
|
mutation({ getters }) {
|
||||||
|
expect(getters).toBeDefined();
|
||||||
|
expect(getters.bestBeerName).toBe("bertinchamps");
|
||||||
|
expect(getters.beerTasterName(1)).toBe("aaron");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const store = new Store({ state, mutations, actions: {}, getters });
|
||||||
|
store.commit("mutation");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("can use getters inside a getter", () => {
|
||||||
|
const getters = {
|
||||||
|
a({ getters }) {
|
||||||
|
return `${getters.b}${getters.c(1)}`;
|
||||||
|
},
|
||||||
|
b() {
|
||||||
|
return 'b';
|
||||||
|
},
|
||||||
|
c() {
|
||||||
|
return i => `c${i}`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const store = new Store({ getters });
|
||||||
|
|
||||||
|
expect(store.getters.a).toBe('bc1');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("advanced state properties", () => {
|
describe("advanced state properties", () => {
|
||||||
@@ -456,6 +581,59 @@ describe("connecting a component to store", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("connect receives store getters as third argument", async () => {
|
||||||
|
const state = {
|
||||||
|
importantID: 1,
|
||||||
|
todos: [
|
||||||
|
{ id: 1, text: "jupiler" },
|
||||||
|
{ id: 2, text: "bertinchamps" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const getters = {
|
||||||
|
importantTodoText({ state }) {
|
||||||
|
return state.todos.find(todo => todo.id === state.importantID).text;
|
||||||
|
},
|
||||||
|
text({ state }) {
|
||||||
|
return id => state.todos.find(todo => todo.id === id).text;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const store = new Store({ state, getters });
|
||||||
|
|
||||||
|
class TodoItem extends Component<any, any, any> {
|
||||||
|
inlineTemplate = `<div>
|
||||||
|
<span><t t-esc="props.activeTodoText"/></span>
|
||||||
|
<span><t t-esc="props.importantTodoText"/></span>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
const ConnectedTodo = connect((state, props, getters) => {
|
||||||
|
const todo = state.todos.find(t => t.id === props.id);
|
||||||
|
return {
|
||||||
|
activeTodoText: getters.text(todo.id),
|
||||||
|
importantTodoText: getters.importantTodoText,
|
||||||
|
};
|
||||||
|
})(TodoItem);
|
||||||
|
|
||||||
|
class TodoList extends Component<any, any, any> {
|
||||||
|
inlineTemplate = `<div>
|
||||||
|
<t t-foreach="props.todos" t-as="todo">
|
||||||
|
<t t-widget="ConnectedTodo" t-props="todo"/>
|
||||||
|
</t>
|
||||||
|
</div>`;
|
||||||
|
widgets = { ConnectedTodo };
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return { todos: state.todos };
|
||||||
|
}
|
||||||
|
const ConnectedTodoList = connect(mapStateToProps)(TodoList);
|
||||||
|
|
||||||
|
(<any>env).store = store;
|
||||||
|
const app = new ConnectedTodoList(env);
|
||||||
|
|
||||||
|
await app.mount(fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<div><div><span>jupiler</span><span>jupiler</span></div><div><span>bertinchamps</span><span>jupiler</span></div></div>");
|
||||||
|
});
|
||||||
|
|
||||||
test("connected component is updated when props are updated", async () => {
|
test("connected component is updated when props are updated", async () => {
|
||||||
class Beer extends Component<any, any, any> {
|
class Beer extends Component<any, any, any> {
|
||||||
inlineTemplate = `<span><t t-esc="props.name"/></span>`;
|
inlineTemplate = `<span><t t-esc="props.name"/></span>`;
|
||||||
|
|||||||
Reference in New Issue
Block a user