简体   繁体   English

如何根据参数设置微光组件的初始状态?

[英]How to set initial state of glimmer component based on argument?

I am struggling to figure out how to implement data down, actions up in a glimmer component hierarchy (using Ember Octane, v3.15).我正在努力弄清楚如何在微光组件层次结构中实现数据向下,向上操作(使用 Ember Octane,v3.15)。

I have a parent component with a list of items.我有一个带有项目列表的父组件。 When the user clicks on a button within the Parent component, I want to populate an Editor component with the data from the relevant item;当用户单击Parent组件中的按钮时,我想用相关项目的数据填充Editor组件; when the user clicks "Save" within the Editor component, populate the changes back to the parent.当用户在Editor组件中单击“保存”时,将更改填充回父级。 Here's what happens instead:这是发生的事情:

我的应用程序的 GIF

How can I make the text box be populated with "Hello", and have changes persisted back to the list above when I click "Save"?如何使文本框填充“Hello”,并在单击“保存”时将更改保留回上面的列表?

Code代码

{{!-- app/components/parent.hbs --}}
<ul>
{{#each this.models as |model|}}
    <li>{{model.text}} <button {{on 'click' (fn this.edit model)}}>Edit</button></li>
{{/each}}
</ul>

<Editor @currentModel={{this.currentModel}} @save={{this.save}} />
// app/components/parent.js
import Component from '@glimmer/component';
export default class ParentComponent extends Component {
    @tracked models = [
        { id: 1, text: 'Hello'},
        { id: 2, text: 'World'}
    ]
    @tracked currentModel = null;

    @action
    edit(model) {
        this.currentModel = model;
    }

    @action
    save(model) {
        // persist data
        this.models = models.map( (m) => m.id == model.id ? model : m )
    }
}
{{!-- app/components/editor.hbs --}}
{{#if @currentModel}}
<small>Editing ID: {{this.id}}</small>
{{/if}}
<Input @value={{this.text}} />
<button {{on 'click' this.save}}>Save</button>
// app/components/editor.hbs
import Component from '@glimmer/component';
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";

export default class EditorComponent extends Component {
    @tracked text;
    @tracked id;

    constructor() {
        super(...arguments)
        if (this.args.currentModel) {
            this.text = this.args.currentModel.text;
            this.id = this.args.currentModel.id;
        }
        
    }

    @action
    save() {
        // persist the updated model back to the parent
        this.args.save({ id: this.id, text: this.text })
    }
}

Rationale/Problem基本原理/问题

I decided to implement Editor as a stateful component, because that seemed like the most idiomatic way to get form data out of the <Input /> component.我决定将Editor实现为有状态组件,因为这似乎是从<Input />组件中获取表单数据的最惯用方式。 I set the initial state using args .我使用args设置初始状态。 Since this.currentModel is @tracked in ParentComponent and I would expect re-assignment of that property to update the @currentModel argument passed to Editor .由于this.currentModelParentComponent中是@tracked ,我希望重新分配该属性以更新传递给Editor@currentModel参数。

Indeed that seems to be the case, since clicking "Edit" next to one of the items in ParentComponent makes <small>Editing ID: {{this.id}}</small> appear.确实情况似乎如此,因为单击ParentComponent中的一项旁边的“编辑”会使<small>Editing ID: {{this.id}}</small>出现。 However, neither the value of the <Input /> element nor the id are populated.但是,既没有填充<Input />元素的值,也没有填充id

I understand that this.text and this.id are not being updated because the constructor of EditorComponent is not being re-run when currentModel changes in the parent... but I'm stuck on what to do instead.我知道this.textthis.id没有被更新,因为当父级中的currentModel更改时, EditorComponentconstructor没有重新运行......但我被困在该怎么做。


What I've tried我试过的

As I was trying to figure this out, I came across this example ( code ), which has pretty much the same interaction between BlogAuthorComponent ( hbs ) and BlogAuthorEditComponent ( hbs , js ).当我试图弄清楚这一点时,我遇到了这个示例code ),它在BlogAuthorComponenthbs )和BlogAuthorEditComponenthbsjs )之间具有几乎相同的交互。 Their solution, as applied to my problem, would be to write EditorComponent like this:他们的解决方案,适用于我的问题,是这样编写 EditorComponent :

{{!-- app/components/editor.hbs --}}
{{#if this.isEditing}}
<small>Editing ID: {{@currentModel.id}}</small>
<Input @value={{@currentModel.text}} />
<button {{on 'click' this.save}}>Save</button>
{{/if}}
// app/components/editor.hbs
import Component from '@glimmer/component';
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";

export default class EditorComponent extends Component {
    get isEditing() {
        return !!this.args.currentModel
    }

    @action
    save() {
        // persist the updated model back to the parent
        this.args.save({ id: this.id, text: this.text })
    }
}

在此处输入图像描述

It works!有用! But I don't like this solution, for a few reasons:但我不喜欢这个解决方案,有几个原因:

  • Modifying a property of something passed to the child component as an arg seems... spooky... I'm honestly not sure why it works at all (since while ParentComponent#models is @tracked , I wouldn't expect properties of POJOs within that array to be followed...)修改作为arg传递给子组件的某些东西的属性似乎......令人毛骨悚然......老实说,我不确定它为什么会起作用(因为虽然ParentComponent#models@tracked ,但我不希望 POJO 的属性在要遵循的数组中...)
  • This updates the text in ParentComponent as you type which, while neat, isn't what I want---I want the changes to be persisted only when the user clicks "Save" (which in this case does nothing)这会在您键入时更新ParentComponent中的文本,虽然整洁,但不是我想要的——我希望只有在用户单击“保存”时才保留更改(在这种情况下什么都不做)
  • In my real app, when the user is not "editing" an existing item, I'd like the form to be an "Add Item" form, where clicking the "Save" button adds a new item.在我的真实应用程序中,当用户没有“编辑”现有项目时,我希望表单成为“添加项目”表单,单击“保存”按钮会添加一个新项目。 I'm not sure how to do this without duplicating the form and/or doing some hairly logic as to what goes in <Input @value ...我不确定如何在不复制表单和/或对<Input @value ...

I also came across this question , but it seems to refer to an old version of glimmer.我也遇到过这个问题,但它似乎指的是旧版本的微光。

Thank you for reading this far---I would appreciate any advice!感谢您阅读这篇文章---我将不胜感激任何建议!

To track changes to currentModel in your editor component and set a default value, use the get accessor:要在editor组件中跟currentModel的更改并设置默认值,请使用get访问器:

get model() {
  return this.args.currentModel || { text: '', id: null };
}

And in your template do:在您的模板中执行以下操作:

{{#if this.model.id}}
  <small>
    Editing ID:
    {{this.model.id}}
  </small>
{{/if}}
<Input @value={{this.model.text}} />
<button type="button" {{on "click" this.save}}>
  Save
</button>

Be aware though that this will mutate currentModel in your parent component, which I guess is not what you want.请注意,这会改变您parent组件中的currentModel ,我猜这不是您想要的。 To circumvent this, create a new object from the properties of the model you're editing.为了避免这种情况,请从您正在编辑的模型的属性中创建一个新对象。

Solution:解决方案:

// editor/component.js
export default class EditorComponent extends Component {
  get model() {
    return this.args.currentModel;
  }

  @action
  save() {
    this.args.save(this.model);
  }
}

In your parent component, create a new object from the passed model.在您的父组件中,从传递的模型创建一个新对象。 Also, remember to reset currentModel in the save action.另外,请记住在保存操作中重置currentModel Now you can just check whether id is null or not in your parent component's save action, and if it is, just implement your save logic:现在您可以在parent组件的save操作中检查id是否为空,如果是,只需实现您的save逻辑:

// parent/component.js
@tracked currentModel = {};

@action
edit(model) {
  // create a new object
  this.currentModel = { ...model };
}

@action
save(model) {
  if (model.id) {
    this.models = this.models.map((m) => (m.id == model.id ? model : m));
  } else {
    // save logic
  }

  this.currentModel = {};
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何根据父组件的 API 调用设置子组件的初始 state? - How to set initial state of child component based on parent component's API call? 如何为 Glimmer 组件指定模板? - How to specify template for Glimmer Component? 如何在不删除初始类名的情况下将类名添加到基于 state 的组件 - How to add className to component based on state without removing initial className 如何基于Rx.ReplaySubject模型在React中设置初始状态 - How to set the initial state in React based on a Rx.ReplaySubject model 如何基于AJAX调用设置Redux Reducer的初始状态? - How to set Redux Reducer's initial state based on AJAX call? 如何在商店中设置初始状态 - How to set initial state in store 如何根据我的初始 state 更新 state - How to update the state based on my initial state 如何在反应 class 组件中设置另一个 state 以将数据从初始 state 传递到 - How can I set another state to pass data to from the initial state in a react class component 如何使用react和typescript根据formfield的值将react state设置为一些初始state? - How to set react state to some initial state based on the value of formfield using react and typescript? 如何在渲染之前将组件初始状态设置为另一个状态? - How can I set a component initial state with another state before render?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM