Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue / Knobs - optimize on force render #4773

Merged
merged 13 commits into from
Nov 20, 2018
Merged

Conversation

igor-dv
Copy link
Member

@igor-dv igor-dv commented Nov 12, 2018

Issue: #4516

What I did

I've added the optimization that should partially address the #4516 problem.

This solves:

In case the knobs are bounded to data() function:

storiesOf('Addon|Knobs', module)
  .addDecorator(withKnobs)
  .add('Simple', () => {
    const name = text('Name', 'John Doe');
    const age = number('Age', 44);

    return {
      components: { SimpleKnobExample },
      template: '<simple-knob-example :name="name" :age="age" />',
      data() {
        return { name, age };
      },
    };
  })

we can easily extract this data and replace it in the existing story

This doesn't solve

The case where the story returns only template:

storiesOf('Addon|Knobs', module)
  .addDecorator(withKnobs)
  .add('All knobs', () => {
    /*a ton of knobs here*/
    return {
      template: `
          <div style="border:2px dotted ${colour}; padding: 8px 22px; border-radius: 8px">
            <h1>My name is ${name},</h1>
            <h3>today is ${new Date(today).toLocaleDateString('en-US', dateOptions)}</h3>
            <p>${stockMessage}</p>
            <p>Also, I have:</p>
            <ul>
              ${items.map(item => `<li key=${item}>${item}</li>`).join('')}
            </ul>
            <p>${salutation}</p>
          </div>
        `,
    };

I don't know how to force update the existing story with this template without recreating the story 🤷‍♂️

CC, @y-nk @pksunkara, would like to hear your thoughts if there are other use-cases when we can just update the component and not destroy it.

@igor-dv igor-dv added bug ci: do not merge addon: knobs vue patch:yes Bugfix & documentation PR that need to be picked to main branch labels Nov 12, 2018
@igor-dv igor-dv self-assigned this Nov 12, 2018
@codecov
Copy link

codecov bot commented Nov 12, 2018

Codecov Report

Merging #4773 into next will decrease coverage by 0.04%.
The diff coverage is 0%.

Impacted file tree graph

@@            Coverage Diff             @@
##             next    #4773      +/-   ##
==========================================
- Coverage   35.47%   35.42%   -0.05%     
==========================================
  Files         561      561              
  Lines        6825     6834       +9     
  Branches      911      913       +2     
==========================================
  Hits         2421     2421              
- Misses       3928     3935       +7     
- Partials      476      478       +2
Impacted Files Coverage Δ
app/react/src/client/preview/render.js 7.69% <ø> (ø) ⬆️
app/vue/src/client/preview/render.js 0% <0%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6005742...2ac83dd. Read the comment docs.

lastStory[key] = value;
});

lastStory.$forceUpdate();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe this will be necessary since Vue as observers for all values of component internal states ; but that won't kill performances either for most of the case, so it's ok to leave it if you feel it's safer.

@y-nk
Copy link
Contributor

y-nk commented Nov 13, 2018

If you are to bind knobs to the internal state, this works completely.

That said, examples of addons-knob for React shows that knobs should be bound to component's properties, not internal state. From the official addons-knob repo, you can easily link back to this example (here's a sample of it) :

stories.add('with some text', () => {
  let content = text('Content', 'This is the content');
  content = content.replace(/\n/g, '<br />');

  return (
    <div
      dangerouslySetInnerHTML={{__html: content}}
    />
  );
});

You can see in this story that the content knob is bound to a property of the returned div. If that div would be a react component, it would still be a prop : storybook wouldn't try to call component.setState({ dangerouslySetInnerHTML: content }) to refresh it, but the Vue version of storybook does. That's what mentioned in my comment : for Vue, render calls are not binding to prop, rather to the internal state of the story.

In the example you provided, you bind the knobs to data, which would actually be the same as binding it to component.state in React, but it should be actually written this way :

storiesOf('Addon|Knobs', module)
  .addDecorator(withKnobs)
  .add('Simple', () => {
    const name = text('Name', 'John Doe');
    const age = number('Age', 44);

    return {
      components: { SimpleKnobExample },
+      props: ['name', 'age'],
      template: '<simple-knob-example :name="name" :age="age" />',
-      data() {
-        return { name, age };
-      },
    };
  })

Then, to make it work, you only need to render the story with component.data() bound the story's properties like that : h(component, { props: component.data() })

Here's a working example (fully in Vue, but only #L57-62 matters) : http://jsfiddle.net/bn8tvpdL/

It will probably fix the 2nd use case since nothing will be changed and Vue's diffing will do the magic.

@y-nk
Copy link
Contributor

y-nk commented Nov 15, 2018

with permission of @igor-dv , i proposed to solve this.

What I did

First i went back to defining the most simple usecase, as it would be in React. Because Vue and React are different, we can't really translate, but what we know is that stories are returning components, and knobs are inserted into properties of those components (for the most part).

Then, I've been recoding the render function. The main idea is to keep the root container alive, and use an outside variable to pass props to the component's story. In order not to break HMR, we still need to recreate a new story everytime it refreshes, but only not if forceRender is true, which seems to link to when knobs are modified (it's undefined when you save the file and HMR reloads it).

Additional notes

It's perfectly ok that the 2nd example destroys/recreate the story every single time. The reason why is normal, is that the template is a full string and is interpreted as such. There's no deep diffing possible within a component when the template string is given in such a way because Vue tries to optimize the template rendering and detects a purely static string.

Such an example should not exist because it doesn't translate from React to Vue paradigm. Knobs should remain properties and derivated values should be computed values.

Copy link
Member Author

@igor-dv igor-dv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've fixed a few minor things. I can't approve since it's my PR

@ndelangen @pksunkara

@igor-dv igor-dv merged commit d609551 into next Nov 20, 2018
@igor-dv igor-dv deleted the vue-knobs-optimize-on-force-render branch November 20, 2018 09:52
@shilman shilman added the patch:done Patch/release PRs already cherry-picked to main/release branch label Nov 21, 2018
shilman pushed a commit that referenced this pull request Nov 21, 2018
…render

Vue / Knobs - optimize on force render
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addon: knobs bug patch:done Patch/release PRs already cherry-picked to main/release branch patch:yes Bugfix & documentation PR that need to be picked to main branch vue
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants