mar*_*ins 215 javascript state reactjs
我正在创建一个用户可以设计自己的表单的应用程序.例如,指定字段的名称以及应包含哪些其他列的详细信息.
该组件在此处作为JSFiddle提供.
我的初始状态如下:
var DynamicForm = React.createClass({
getInitialState: function() {
var items = {};
items[1] = { name: 'field 1', populate_at: 'web_start',
same_as: 'customer_name',
autocomplete_from: 'customer_name', title: '' };
items[2] = { name: 'field 2', populate_at: 'web_end',
same_as: 'user_name',
autocomplete_from: 'user_name', title: '' };
return { items };
},
render: function() {
var _this = this;
return (
<div>
{ Object.keys(this.state.items).map(function (key) {
var item = _this.state.items[key];
return (
<div>
<PopulateAtCheckboxes this={this}
checked={item.populate_at} id={key}
populate_at={data.populate_at} />
</div>
);
}, this)}
<button onClick={this.newFieldEntry}>Create a new field</button>
<button onClick={this.saveAndContinue}>Save and Continue</button>
</div>
);
}
Run Code Online (Sandbox Code Playgroud)
我想在用户更改任何值时更新状态,但我很难定位正确的对象:
var PopulateAtCheckboxes = React.createClass({
handleChange: function (e) {
item = this.state.items[1];
item.name = 'newName';
items[1] = item;
this.setState({items: items});
},
render: function() {
var populateAtCheckbox = this.props.populate_at.map(function(value) {
return (
<label for={value}>
<input type="radio" name={'populate_at'+this.props.id} value={value}
onChange={this.handleChange} checked={this.props.checked == value}
ref="populate-at"/>
{value}
</label>
);
}, this);
return (
<div className="populate-at-checkboxes">
{populateAtCheckbox}
</div>
);
}
});
Run Code Online (Sandbox Code Playgroud)
我该如何制作this.setState
才能让它更新items[1].name
?
Jon*_*nan 109
您可以使用update
immutability helper:
this.setState({
items: update(this.state.items, {1: {name: {$set: 'updated field name'}}})
})
Run Code Online (Sandbox Code Playgroud)
或者如果您不关心能否在shouldComponentUpdate()
生命周期方法中检测到对此项目的更改===
,您可以直接编辑状态并强制组件重新渲染 - 这实际上与@limelights的答案相同,因为它是将对象拉出状态并进行编辑.
this.state.items[1].name = 'updated field name'
this.forceUpdate()
Run Code Online (Sandbox Code Playgroud)
编辑后添加:
查看react-training中的Simple Component Communication课程,了解如何将回调函数从状态保持父项传递到需要触发状态更改的子组件.
Mar*_*nVK 85
错误的方法!
handleChange = (e) => {
const { items } = this.state;
items[1].name = e.target.value;
// update state
this.setState({
items,
});
};
Run Code Online (Sandbox Code Playgroud)
正如许多更好的开发人员在评论中指出的那样:改变状态是错误的!
我花了一段时间才弄明白这一点.以上工作,但它夺走了React的力量.例如,componentDidUpdate
不会将此视为更新,因为它是直接修改的.
所以正确的方法是:
handleChange = (e) => {
this.setState(prevState => ({
items: {
...prevState.items,
[prevState.items[1].name]: e.target.value,
},
}));
};
Run Code Online (Sandbox Code Playgroud)
mpe*_*pen 83
由于此线程中存在大量错误信息,因此无需帮助库就可以执行以下操作:
handleChange: function (e) {
// 1. Make a shallow copy of the items
let items = [...this.state.items];
// 2. Make a shallow copy of the item you want to mutate
let item = {...items[1]};
// 3. Replace the property you're intested in
item.name = 'newName';
// 4. Put it back into our array. N.B. we *are* mutating the array here, but that's why we made a copy first
items[1] = item;
// 5. Set the state to our new copy
this.setState({items});
},
Run Code Online (Sandbox Code Playgroud)
如果需要,您可以组合步骤2和3:
let item = {
...items[1],
name: 'newName'
}
Run Code Online (Sandbox Code Playgroud)
或者你可以在一行中完成整个事情:
this.setState(({items}) => ({
items: [
...items.slice(0,1),
{
...items[1],
name: 'newName',
},
...items.slice(2)
]
}));
Run Code Online (Sandbox Code Playgroud)
注意:我做了items
一个数组.OP使用了一个对象.但是,概念是相同的.
您可以在终端/控制台中查看正在发生的事情:
? node
> items = [{name:'foo'},{name:'bar'},{name:'baz'}]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> clone = [...items]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> item1 = {...clone[1]}
{ name: 'bar' }
> item1.name = 'bacon'
'bacon'
> clone[1] = item1
{ name: 'bacon' }
> clone
[ { name: 'foo' }, { name: 'bacon' }, { name: 'baz' } ]
> items
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ] // good! we didn't mutate `items`
> items === clone
false // these are different objects
> items[0] === clone[0]
true // we don't need to clone items 0 and 2 because we're not mutating them (efficiency gains!)
> items[1] === clone[1]
false // this guy we copied
Run Code Online (Sandbox Code Playgroud)
Tra*_*oud 47
若要修改作出反应的状态深深嵌套的对象/变量,通常使用三种方法:香草JS Object.assign
,不变性辅助和cloneDeep
从Lodash.还有很多其他不太受欢迎的第三方库来实现这一目标,但在这个答案中,我将只介绍这三个选项.此外,还有一些方法只使用vanilla JavaScript,比如数组传播(参见@ mpen的答案),但它们不是非常直观,易于使用并且能够处理所有状态操作情况.
正如无数次在最高投票评论中指出答案,其作者提出了国家的直接变异,就是不要这样做.这是一种无处不在的React反模式,它将不可避免地导致不必要的后果.学习正确的方法.
让我们比较三种广泛使用的方法.
鉴于这种状态结构:
state = {
outer: {
inner: 'initial value'
}
}
Run Code Online (Sandbox Code Playgroud)
const App = () => {
const [outer, setOuter] = React.useState({ inner: 'initial value' })
React.useEffect(() => {
console.log('Before the shallow copying:', outer.inner) // initial value
const newOuter = Object.assign({}, outer, { inner: 'updated value' })
console.log('After the shallow copy is taken, the value in the state is still:', outer.inner) // initial value
setOuter(newOuter)
}, [])
console.log('In render:', outer.inner)
return (
<section>Inner property: <i>{outer.inner}</i></section>
)
}
ReactDOM.render(
<App />,
document.getElementById('react')
)
Run Code Online (Sandbox Code Playgroud)
请记住,这inner
不会执行深度克隆,因为它只复制属性值,这就是为什么它的作用称为浅复制(请参阅注释).
对象属性是此对象的顶级项(outer.inner
),它们的值可以是基元(字符串,数字等)或对其他对象(对象,数组)的引用.
因此,如果您具有相对简单的一级深度状态结构,其中最内层成员保持基本类型的值,则它将起作用.就像const newOuter...
在这个例子中一样,持有字符串.
如果您有更深的对象(第二级或更高级别),您应该更新,请不要使用Object.assign
.你冒险直接改变国家.
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<main id="react"></main>
Run Code Online (Sandbox Code Playgroud)
基本上,Lodash {}
执行深度克隆,因此如果您具有包含多级对象或数组的相当复杂的状态,则它是一个强大的选项.
const App = () => {
const [outer, setOuter] = React.useState({ inner: 'initial value' })
React.useEffect(() => {
console.log('Before the deep cloning:', outer.inner) // initial value
const newOuter = _.cloneDeep(outer) // cloneDeep() is coming from the Lodash lib
newOuter.inner = 'updated value'
console.log('After the deeply cloned object is modified, the value in the state is still:', outer.inner) // initial value
setOuter(newOuter)
}, [])
console.log('In render:', outer.inner)
return (
<section>Inner property: <i>{outer.inner}</i></section>
)
}
ReactDOM.render(
<App />,
document.getElementById('react')
)
Run Code Online (Sandbox Code Playgroud)
关于不变性辅助很酷的事情是,它可以outer
值状态的项目,也{ inner: 'initial value' }
,{ inner: 'updated value' }
,newOuter
(等)它们.这是可用命令列表.
请注意,{ inner: 'updated value' }
它只修改状态对象的第一级属性(inner
在本例中为属性),而不是深层嵌套(newOuter
).如果它以另一种方式表现,那么这个问题就不存在了.
顺便说一下,setOuter()
这只是一个简写outer
.而newOuter
之后的outer
是之后被立即执行的回调state = { outer: { inner: { innerMost: 'initial value' } } }
已设置的状态.方便的话,如果你需要在它完成工作后做一些事情(在我们的情况下,在它设置后立即显示状态).
现在,当涵盖所有三种主要方法时,让我们比较它们的速度.我为此创建了一个jsperf.在此测试中,具有10000个具有随机值的属性的巨大状态由上述所有三种方法操纵.
以下是我在Windows 10上使用Chrome 61.0.3163的结果:
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
<main id="react"></main>
Run Code Online (Sandbox Code Playgroud)
请注意,在同一台机器上的Edge中,Lodash的表现比其他两种方法差.在Firefox中,有时候newOuter
会成为胜利者,但在其他时候却是胜利者outer
.
这是jsperf.com测试的链接.
如果您不想或可以使用外部依赖项,并且具有简单的状态结构,请坚持使用Object.assign
.
如果你操纵一个巨大的和/或复杂的状态,Lodash innerMost
是一个明智的选择.
如果您需要高级功能,即如果您的状态结构很复杂并且您需要对其执行各种操作,请尝试newOuter
,它是一种非常先进的工具,可用于状态操作.
小智 29
我有同样的问题.这是一个有效的简单解决方案!
const newItems = [...this.state.items];
newItems[item] = value;
this.setState({ items:newItems });
Run Code Online (Sandbox Code Playgroud)
Hen*_*son 27
首先获取所需的项目,在该对象上更改所需内容并将其设置回状态.getInitialState
如果您使用键控对象,那么通过仅传递对象来使用状态的方式会更容易.
handleChange: function (e) {
item = this.state.items[1];
item.name = 'newName';
items[1] = item;
this.setState({items: items});
}
Run Code Online (Sandbox Code Playgroud)
eic*_*ksl 27
根据关于setState的React文档,使用Object.assign
其他答案的建议并不理想.由于setState
异步行为的性质,使用此技术的后续调用可能会覆盖先前的调用,从而导致不良结果.
相反,React文档建议使用setState
以前状态运行的更新程序形式.请记住,在更新数组或对象时,必须返回一个新数组或对象,因为React要求我们保留状态不变性.使用ES6语法的扩展运算符来浅层复制数组,在数组的给定索引处创建或更新对象的属性将如下所示:
this.setState(prevState => {
const newItems = [...prevState.items];
newItems[index].name = newName;
return {items: newItems};
})
Run Code Online (Sandbox Code Playgroud)
小智 22
不要改变状态.它可能会导致意外的结果.我已经吸取了教训!始终使用副本/克隆,Object.assign()
是一个很好的:
item = Object.assign({}, this.state.items[1], {name: 'newName'});
items[1] = item;
this.setState({items: items});
Run Code Online (Sandbox Code Playgroud)
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
jam*_*mes 11
有时在React中,改变克隆数组可能会影响原始数组,这种方法永远不会导致变异:
const myNewArray = Object.assign([...myArray], {
[index]: myNewItem
});
setState({ myArray: myNewArray });
Run Code Online (Sandbox Code Playgroud)
或者,如果您只想更新项目的属性:
const myNewArray = Object.assign([...myArray], {
[index]: {
...myArray[index],
prop: myNewValue
}
});
setState({ myArray: myNewArray });
Run Code Online (Sandbox Code Playgroud)
由于上述选项对我来说都不理想,因此我最终使用了地图:
this.setState({items: this.state.items.map((item,idx)=> idx!==1 ?item :{...item,name:'new_name'}) })
Run Code Online (Sandbox Code Playgroud)
小智 5
在一行中使用带箭头函数的数组映射
this.setState({
items: this.state.items.map((item, index) =>
index === 1 ? { ...item, name: 'newName' } : item,
)
})
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
247013 次 |
最近记录: |