[REF] connected component: separate storeprops from props

closes #259
This commit is contained in:
Géry Debongnie
2019-08-23 17:34:29 +02:00
parent 2294c1967f
commit 34e463ad0a
2 changed files with 84 additions and 101 deletions
+59 -76
View File
@@ -12,19 +12,20 @@ export class ConnectedComponent<T extends Env, P, S> extends Component<T, P, S>
return env.store; return env.store;
} }
hashFunction: HashFunction = ({ storeProps }, options) => { storeProps: any;
const observer = (this.__owl__ as any).store.observer;
let refFunction = this.deep ? observer.deepRevNumber : observer.revNumber; hashFunction: HashFunction = (storeProps, options) => {
if ("__owl__" in storeProps) { const revFn = (this.__owl__ as any).revFn;
return refFunction.call(observer, storeProps); const rev = revFn(storeProps);
if (rev > 0) {
return rev;
} }
const { currentStoreProps } = options;
let hash = 0; let hash = 0;
for (let key in storeProps) { for (let key in storeProps) {
const val = storeProps[key]; const val = storeProps[key];
const hashVal = refFunction.call(observer, val); const hashVal = revFn(val);
if (hashVal === 0) { if (hashVal === 0) {
if (val !== currentStoreProps[key]) { if (val !== options.prevStoreProps[key]) {
options.didChange = true; options.didChange = true;
} }
} else { } else {
@@ -37,32 +38,23 @@ export class ConnectedComponent<T extends Env, P, S> extends Component<T, P, S>
static mapStoreToProps(storeState, ownProps, getters) { static mapStoreToProps(storeState, ownProps, getters) {
return {}; return {};
} }
constructor(parent, props?: any) {
super(parent, props);
/**
* Need to do this here so 'deep' can be overrided by subcomponent easily
*/
async __prepareAndRender(scope?: Object, vars?: any): ReturnType<Component<any,any,any>["__prepareAndRender"]> {
const store = this.getStore(this.env); const store = this.getStore(this.env);
const ownProps = this.props || {}; const ownProps = this.props || {};
const storeProps = (<any>this.constructor).mapStoreToProps( this.storeProps = (<any>this.constructor).mapStoreToProps(store.state, ownProps, store.getters);
store.state, const observer = store.observer;
ownProps, const revFn = this.deep ? observer.deepRevNumber : observer.revNumber;
store.getters (this.__owl__ as any).store = store;
); (this.__owl__ as any).revFn = revFn.bind(observer);
const mergedProps = Object.assign({}, ownProps, storeProps); (this.__owl__ as any).storeHash = this.hashFunction(this.storeProps, {
prevStoreProps: this.storeProps
this.props = mergedProps; });
(this.__owl__ as any).rev = observer.rev;
(<any>this.__owl__).ownProps = ownProps; return super.__prepareAndRender(scope, vars);
(<any>this.__owl__).currentStoreProps = storeProps;
(<any>this.__owl__).store = store;
(<any>this.__owl__).storeHash = this.hashFunction(
{
state: store.state,
storeProps: storeProps
},
{
currentStoreProps: storeProps
}
);
} }
/** /**
* We do not use the mounted hook here for a subtle reason: we want the * We do not use the mounted hook here for a subtle reason: we want the
@@ -70,56 +62,47 @@ export class ConnectedComponent<T extends Env, P, S> extends Component<T, P, S>
* if we use the mounted hook, this will be done in the reverse order. * if we use the mounted hook, this will be done in the reverse order.
*/ */
__callMounted() { __callMounted() {
(<any>this.__owl__).store.on("update", this, this.__checkUpdate); (this.__owl__ as any).store.on("update", this, this.__checkUpdate);
super.__callMounted(); super.__callMounted();
} }
willUnmount() { __callWillUnmount() {
(<any>this.__owl__).store.off("update", this); (this.__owl__ as any).store.off("update", this);
super.willUnmount(); super.__callWillUnmount();
} }
async __checkUpdate(updateId) { async __updateProps(nextProps: P, f, p, s, v) {
if (updateId === (<any>this.__owl__).currentUpdateId) { this.__updateStoreProps(nextProps);
return; return super.__updateProps(nextProps, f, p, s, v);
}
const ownProps = (<any>this.__owl__).ownProps;
const storeProps = (<any>this.constructor).mapStoreToProps(
(<any>this.__owl__).store.state,
ownProps,
(<any>this.__owl__).store.getters
);
const options: any = {
currentStoreProps: (<any>this.__owl__).currentStoreProps
};
const storeHash = this.hashFunction(
{
state: (<any>this.__owl__).store.state,
storeProps: storeProps
},
options
);
let didChange = options.didChange;
if (storeHash !== (<any>this.__owl__).storeHash) {
didChange = true;
(<any>this.__owl__).storeHash = storeHash;
}
if (didChange) {
(<any>this.__owl__).currentStoreProps = storeProps;
await this.__updateProps(ownProps, false);
}
} }
__updateProps(nextProps, forceUpdate, patchQueue?: any[]) {
const __owl__ = <any>this.__owl__; __updateStoreProps(nextProps): boolean {
__owl__.currentUpdateId = __owl__.store._updateId; const store = (this.__owl__ as any).store;
if (__owl__.ownProps !== nextProps) { const storeProps = (<any>this.constructor).mapStoreToProps(
__owl__.currentStoreProps = (<any>this.constructor).mapStoreToProps( store.state,
__owl__.store.state, nextProps,
nextProps, store.getters
__owl__.store.getters );
); const options = { prevStoreProps: this.storeProps, didChange: false };
const storeHash = this.hashFunction(storeProps, options);
this.storeProps = storeProps;
let didChange = options.didChange;
if (storeHash !== (this.__owl__ as any).storeHash) {
(this.__owl__ as any).storeHash = storeHash;
didChange = true;
}
(this.__owl__ as any).rev = store.observer.rev;
return didChange
}
async __checkUpdate() {
const observer = (this.__owl__ as any).store.observer;
if (observer.rev === (this.__owl__ as any).rev) {
// update was already done by updateProps, from parent
return;
}
const didChange = this.__updateStoreProps(this.props);
if (didChange) {
this.render();
} }
__owl__.ownProps = nextProps;
const mergedProps = Object.assign({}, nextProps, __owl__.currentStoreProps);
return super.__updateProps(mergedProps, forceUpdate, patchQueue);
} }
} }
+25 -25
View File
@@ -20,7 +20,7 @@ describe("connecting a component to store", () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="App"> <div t-name="App">
<t t-foreach="props.todos" t-as="todo" > <t t-foreach="storeProps.todos" t-as="todo" >
<Todo msg="todo.msg" t-key="todo"/> <Todo msg="todo.msg" t-key="todo"/>
</t> </t>
</div> </div>
@@ -56,7 +56,7 @@ describe("connecting a component to store", () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="App"> <div t-name="App">
<span t-foreach="props.todos" t-as="todo" t-key="todo"> <span t-foreach="storeProps.todos" t-as="todo" t-key="todo">
<t t-esc="todo.title"/> <t t-esc="todo.title"/>
</span> </span>
</div> </div>
@@ -104,7 +104,7 @@ describe("connecting a component to store", () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="App"> <div t-name="App">
<t t-foreach="props.todos" t-as="todo"> <t t-foreach="storeProps.todos" t-as="todo">
<Todo msg="todo.msg" t-key="todo" /> <Todo msg="todo.msg" t-key="todo" />
</t> </t>
</div> </div>
@@ -196,9 +196,9 @@ describe("connecting a component to store", () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<span t-name="TodoItem"><t t-esc="props.text"/></span> <span t-name="TodoItem"><t t-esc="storeProps.text"/></span>
<div t-name="TodoList"> <div t-name="TodoList">
<t t-foreach="props.todos" t-as="todo"> <t t-foreach="storeProps.todos" t-as="todo">
<TodoItem id="todo.id" t-key="todo.id"/> <TodoItem id="todo.id" t-key="todo.id"/>
</t> </t>
</div> </div>
@@ -247,11 +247,11 @@ describe("connecting a component to store", () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="TodoItem"> <div t-name="TodoItem">
<span><t t-esc="props.activeTodoText"/></span> <span><t t-esc="storeProps.activeTodoText"/></span>
<span><t t-esc="props.importantTodoText"/></span> <span><t t-esc="storeProps.importantTodoText"/></span>
</div> </div>
<div t-name="TodoList"> <div t-name="TodoList">
<t t-foreach="props.todos" t-as="todo"> <t t-foreach="storeProps.todos" t-as="todo">
<TodoItem id="todo.id" t-key="todo.id"/> <TodoItem id="todo.id" t-key="todo.id"/>
</t> </t>
</div> </div>
@@ -287,7 +287,7 @@ describe("connecting a component to store", () => {
test("connected component is updated when props are updated", async () => { test("connected component is updated when props are updated", async () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<span t-name="Beer"><t t-esc="props.name"/></span> <span t-name="Beer"><t t-esc="storeProps.name"/></span>
<div t-name="App"> <div t-name="App">
<Beer id="state.beerId"/> <Beer id="state.beerId"/>
</div> </div>
@@ -322,7 +322,7 @@ describe("connecting a component to store", () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="App"> <div t-name="App">
<span t-foreach="props.beers" t-as="beer" t-key="beer.name"><t t-esc="beer.name"/></span> <span t-foreach="storeProps.beers" t-as="beer" t-key="beer.name"><t t-esc="beer.name"/></span>
</div> </div>
</templates> </templates>
`); `);
@@ -357,9 +357,9 @@ describe("connecting a component to store", () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="Beer"> <div t-name="Beer">
<span>taster:<t t-esc="props.taster"/></span> <span>taster:<t t-esc="storeProps.taster"/></span>
<span t-if="props.selected">selected:<t t-esc="props.selected.name"/></span> <span t-if="storeProps.selected">selected:<t t-esc="storeProps.selected.name"/></span>
<span t-if="props.consumed">consumed:<t t-esc="props.consumed.name"/></span> <span t-if="storeProps.consumed">consumed:<t t-esc="storeProps.consumed.name"/></span>
</div> </div>
<div t-name="App"> <div t-name="App">
<Beer id="state.beerId"/> <Beer id="state.beerId"/>
@@ -424,9 +424,9 @@ describe("connecting a component to store", () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="Beer"> <div t-name="Beer">
<span>taster:<t t-esc="props.taster"/></span> <span>taster:<t t-esc="storeProps.taster"/></span>
<span t-if="props.selected">selected:<t t-esc="props.selected.name"/></span> <span t-if="storeProps.selected">selected:<t t-esc="storeProps.selected.name"/></span>
<span t-if="props.consumed">consumed:<t t-esc="props.consumed.name"/></span> <span t-if="storeProps.consumed">consumed:<t t-esc="storeProps.consumed.name"/></span>
</div> </div>
<div t-name="App"> <div t-name="App">
<Beer id="state.beerId"/> <Beer id="state.beerId"/>
@@ -517,9 +517,9 @@ describe("connecting a component to store", () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="Parent"> <div t-name="Parent">
<Child key="props.current"/> <Child key="storeProps.current"/>
</div> </div>
<span t-name="Child"><t t-esc="props.msg"/></span> <span t-name="Child"><t t-esc="storeProps.msg"/></span>
</templates> </templates>
`); `);
@@ -577,13 +577,13 @@ describe("connecting a component to store", () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="TodoApp" class="todoapp"> <div t-name="TodoApp" class="todoapp">
<t t-foreach="Object.values(props.todos)" t-as="todo"> <t t-foreach="Object.values(storeProps.todos)" t-as="todo">
<TodoItem t-key="todo.id" id="todo.id"/> <TodoItem t-key="todo.id" id="todo.id"/>
</t> </t>
</div> </div>
<div t-name="TodoItem" class="todo"> <div t-name="TodoItem" class="todo">
<t t-esc="props.todo.title"/> <t t-esc="storeProps.todo.title"/>
<button class="destroy" t-on-click="editTodo">x</button> <button class="destroy" t-on-click="editTodo">x</button>
</div> </div>
</templates> </templates>
@@ -656,13 +656,13 @@ describe("connecting a component to store", () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="TodoApp" class="todoapp"> <div t-name="TodoApp" class="todoapp">
<t t-foreach="Object.values(props.todos)" t-as="todo"> <t t-foreach="Object.values(storeProps.todos)" t-as="todo">
<TodoItem t-key="todo.id" id="todo.id"/> <TodoItem t-key="todo.id" id="todo.id"/>
</t> </t>
</div> </div>
<div t-name="TodoItem" class="todo"> <div t-name="TodoItem" class="todo">
<t t-esc="props.todo.title"/> <t t-esc="storeProps.todo.title"/>
<button class="destroy" t-on-click="removeTodo">x</button> <button class="destroy" t-on-click="removeTodo">x</button>
</div> </div>
</templates> </templates>
@@ -720,7 +720,7 @@ describe("connecting a component to store", () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="App"><t t-esc="props.msg"/></div> <div t-name="App"><t t-esc="storeProps.msg"/></div>
</templates> </templates>
`); `);
@@ -829,11 +829,11 @@ describe("connected components and default values", () => {
<templates> <templates>
<div t-name="Message"> <div t-name="Message">
<t t-if="props.showId"><t t-esc="props.messageId"/></t> <t t-if="props.showId"><t t-esc="props.messageId"/></t>
<t t-esc="props.message.content"/> <t t-esc="storeProps.message.content"/>
</div> </div>
<div t-name="Thread"> <div t-name="Thread">
<t t-if="props.showMessages"> <t t-if="props.showMessages">
<Message t-foreach="props.thread.messages" t-as="messageId" messageId="messageId" t-key="messageId"/> <Message t-foreach="storeProps.thread.messages" t-as="messageId" messageId="messageId" t-key="messageId"/>
</t> </t>
</div> </div>
<div t-name="App"><Thread threadId="props.threadId"/></div> <div t-name="App"><Thread threadId="props.threadId"/></div>