TLDR:在不违反 React 原则或忽略标准封装机会的情况下,React 中的常见设计要求似乎是不可能的。
我有一个表单可以显示来自服务器 API 的现有数据,并允许用户编辑字段并更新服务器上的数据。这在用户更改表单输入值时动态发生,而不是要求他们在每次编辑后“提交”表单。
对于某些表单输入,值会立即更改(例如复选框、单选按钮、选择)。对于其他人来说,值(value)的变化是递增的——最明显的是文本输入。我不希望在每次击键时都请求服务器,因为这会导致为不完整的值生成服务器端验证错误。相反,一旦用户离开文本输入字段,服务器就会更新。如果值不变,发送服务器请求也是一种浪费。
在 React 中,关键的设计原则似乎是您维护单一的真相/状态来源,并使用 props 将其渗透到组件中。组件不应该将自己的状态作为 props 的副本来维护,而应该直接渲染 props [1]。 “单一来源”将是从服务器拉取数据和向服务器推送数据的父组件。
对于呈现的 <input>元素以使其值与服务器更改保持同步,它应该使用 value属性,因为 defaultValue仅在第一次渲染时评估 [2]。 React 设计原则暗示我应该设置 <input value={this.props.value} /> .为了响应用户输入,onChange还必须提供处理程序,将更改冒泡到父组件,父组件将更新状态并导致 <input>使用更新的 props 重新渲染.
但是,我不想在 onChange 中触发服务器请求处理程序,因为这将在每次击键时触发。我需要在 onBlur 上触发服务器请求事件,假设该值自 onFocus 以来发生了变化.对某些元素而不是其他元素要求此意味着父组件将需要两个处理程序:onChange。处理程序更新所有子组件的状态并为某些字段触发服务器请求,以及 onBlur触发对其他字段的服务器请求。要求父组件知道哪些子表单组件应该表现出哪些行为似乎无法正确封装。子组件应该能够监控自己的值并决定何时发出“做某事”事件。
我看不出有什么方法可以在不违反 React 原则的情况下实现这一点,很可能是通过在每个表单组件中维护一个状态。像这样:
class TextInput extends React.Component {
constructor(props) {
super(props);
this.initialValue = this.props.value;
this.setState({value: this.props.value});
}
componentWillReceiveProps = (nextProps) => {
this.initialValue = nextProps.value;
this.setState({value: nextProps.value});
};
handleFocus = (e) => {
this.initialValue = e.target.value;
};
handleChange = (e) => {
this.setState({value: e.target.value});
};
handleBlur = (e) => {
if (e.target.value !== this.initialValue &&
this.props.handleChange) {
this.props.handleChange(e);
}
};
render() {
return (
<input type="text"
value={this.state.value}
onFocus={this.handleFocus}
onChange={this.handleChange}
onBlur={this.handleBlur} />
);
}
}
class FormHandler extends React.Component {
componentDidMount() {
// fetch from API...
this.setState(apiResponse);
}
handleChange = (e) => {
// update API with e.target.value....
};
render() {
return (<TextInput value={this.state.value}
handleChange={this.handleChange} />);
}
}
有没有更好的方法来实现这一点,同时又不违反 React 的 props 滴入渲染原则?
进一步阅读 [3-4] 中解决此问题的各种尝试。
在 [5-6] 中遇到类似问题的人们提出的其他 SO 问题。
[1] https://facebook.github.io/react/tips/props-in-getInitialState-as-anti-pattern.html
[2] https://facebook.github.io/react/docs/forms.html#default-value
[3] https://discuss.reactjs.org/t/how-to-pass-in-initial-value-to-form-fields/869
[4] https://blog.iansinnott.com/managing-state-and-controlled-form-fields-with-react/
[5] How do I reset the defaultValue for a React input
[6] Using an input field with onBlur and a value from state blocks input in Reactjs JSX?
最佳答案
您显然花了很多时间思考这个问题并查找相关讨论。就其值(value)而言,我认为您实际上过度思考了这个问题。 React 的核心是一个非常实用的工具。它会将您推向某些方向,并且肯定有一些被认为是惯用的方法,但它提供了许多逃生舱口,使您可以突破基本方法以适应您的特定用例。因此,如果您需要以特定方式构建事物以使您的应用程序按照您想要的方式运行,请不要花时间担心它是否“绝对完全符合 React 的原则”。没有人会因为你背离了一些神话般的理想而对你大喊大叫:)
是的,您提供的示例代码乍一看似乎非常合理。
关于javascript - 在 React 中处理受控的表单组件更改,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39165599/
如何正确创建Rails迁移,以便将表更改为MySQL中的MyISAM?目前是InnoDB。运行原始执行语句会更改表,但它不会更新db/schema.rb,因此当在测试环境中重新创建表时,它会返回到InnoDB并且我的全文搜索失败。我如何着手更改/添加迁移,以便将现有表修改为MyISAM并更新schema.rb,以便我的数据库和相应的测试数据库得到相应更新? 最佳答案 我没有找到执行此操作的好方法。您可以像有人建议的那样更改您的schema.rb,然后运行:rakedb:schema:load,但是,这将覆盖您的数据。我的做法是(假设
我得到了一个包含嵌套链接的表单。编辑时链接字段为空的问题。这是我的表格:Editingkategori{:action=>'update',:id=>@konkurrancer.id})do|f|%>'Trackingurl',:style=>'width:500;'%>'Editkonkurrence'%>|我的konkurrencer模型:has_one:link我的链接模型:classLink我的konkurrancer编辑操作:defedit@konkurrancer=Konkurrancer.find(params[:id])@konkurrancer.link_attrib
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘
我尝试使用不同的ssh_options在同一阶段运行capistranov.3任务。我的production.rb说:set:stage,:productionset:user,'deploy'set:ssh_options,{user:'deploy'}通过此配置,capistrano与用户deploy连接,这对于其余的任务是正确的。但是我需要将它连接到服务器中配置良好的an_other_user以完成一项特定任务。然后我的食谱说:...taskswithoriginaluser...task:my_task_with_an_other_userdoset:user,'an_othe
我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser
假设我有一个FireNinja我的数据库中的对象,使用单表继承存储。后来才知道他真的是WaterNinja.将他更改为不同的子类的最干净的方法是什么?更好的是,我很想创建一个新的WaterNinja对象并替换旧的FireNinja在数据库中,保留ID。编辑我知道如何创建新的WaterNinja来self现有FireNinja的对象,我也知道我可以删除旧的并保存新的。我想做的是改变现有项目的类别。我是通过创建一个新对象并执行一些ActiveRecord魔法来替换行,还是通过对对象本身做一些疯狂的事情,或者甚至通过删除它并使用相同的ID重新插入来做到这一点,这是问题的一部分。
我想解析一个已经存在的.mid文件,改变它的乐器,例如从“acousticgrandpiano”到“violin”,然后将它保存回去或作为另一个.mid文件。根据我在文档中看到的内容,该乐器通过program_change或patch_change指令进行了更改,但我找不到任何在已经存在的MIDI文件中执行此操作的库.他们似乎都只支持从头开始创建的MIDI文件。 最佳答案 MIDIpackage会为您完成此操作,但具体方法取决于midi文件的原始内容。一个MIDI文件由一个或多个音轨组成,每个音轨是十六个channel中任何一个上的
我最喜欢的Google文档功能之一是它会在我工作时不断自动保存我的文档版本。这意味着即使我在进行关键更改之前忘记在某个点进行保存,也很有可能会自动创建一个保存点。至少,我可以将文档恢复到错误更改之前的状态,并从该点继续工作。对于在MacOS(或UNIX)上运行的Ruby编码器,是否有具有等效功能的工具?例如,一个工具会每隔几分钟自动将Gitcheckin我的本地存储库以获取我正在处理的文件。也许我有点偏执,但这点小保险可以让我在日常工作中安心。 最佳答案 虚拟机有些人可能讨厌我对此的回应,但我在编码时经常使用VIM,它具有自动保存功
我想在IRB中浏览文件系统并让提示更改以反射(reflect)当前工作目录,但我不知道如何在每个命令后进行提示更新。最终,我想在日常工作中更多地使用IRB,让bash溜走。我在我的.irbrc中试过这个:require'fileutils'includeFileUtilsIRB.conf[:PROMPT][:CUSTOM]={:PROMPT_N=>"\e[1m:\e[m",:PROMPT_I=>"\e[1m#{pwd}>\e[m",:PROMPT_S=>"FOO",:PROMPT_C=>"\e[1m#{pwd}>\e[m",:RETURN=>""}IRB.conf[:PROMPT_MO