Phản ứng: làm thế nào để cập nhật state.item [1] trong trạng thái bằng setState?


259

Tôi đang tạo một ứng dụng mà người dùng có thể thiết kế biểu mẫu của riêng mình. Ví dụ: chỉ định tên của trường và chi tiết về các cột khác cần được đưa vào.

Thành phần này có sẵn dưới dạng một JSFiddle ở đây .

Trạng thái ban đầu của tôi trông như thế này:

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>
    );
  }

Tôi muốn cập nhật trạng thái khi người dùng thay đổi bất kỳ giá trị nào, nhưng tôi gặp khó khăn khi nhắm mục tiêu đúng đối tượng:

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>
    );
  }
});

Làm thế nào tôi nên craft this.setStateđể có được nó để cập nhật items[1].name?


được liên kết - stackoverflow.com/q/26253351/104380
vsync

1
câu trả lời được lựa chọn của bạn cho câu hỏi này là gì?
Braian Mellor

Câu trả lời:


126

Bạn có thể sử dụng trình updatetrợ giúp bất biến cho việc này :

this.setState({
  items: update(this.state.items, {1: {name: {$set: 'updated field name'}}})
})

Hoặc nếu bạn không quan tâm đến việc có thể phát hiện các thay đổi đối với mặt hàng này trong shouldComponentUpdate()phương pháp vòng đời bằng cách sử dụng ===, bạn có thể chỉnh sửa trạng thái trực tiếp và buộc thành phần kết xuất lại - đây thực sự giống như câu trả lời của @limelights, vì nó kéo một đối tượng ra khỏi trạng thái và chỉnh sửa nó.

this.state.items[1].name = 'updated field name'
this.forceUpdate()

Bổ sung sau chỉnh sửa:

Kiểm tra bài học Giao tiếp thành phần đơn giản từ đào tạo phản ứng để biết ví dụ về cách chuyển chức năng gọi lại từ cha mẹ giữ trạng thái sang thành phần con cần kích hoạt thay đổi trạng thái.


125

Vì có rất nhiều thông tin sai trong chủ đề này, đây là cách bạn có thể làm điều đó mà không cần người trợ giúp libs:

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});
},

Bạn có thể kết hợp bước 2 và 3 nếu bạn muốn:

let item = {
    ...items[1],
    name: 'newName'
}

Hoặc bạn có thể làm toàn bộ trong một dòng:

this.setState(({items}) => ({
    items: [
        ...items.slice(0,1),
        {
            ...items[1],
            name: 'newName',
        },
        ...items.slice(2)
    ]
}));

Lưu ý: Tôi đã tạo itemsmột mảng. OP đã sử dụng một đối tượng. Tuy nhiên, các khái niệm là như nhau.


Bạn có thể thấy những gì đang diễn ra trong thiết bị đầu cuối / bảng điều khiển của bạn:

 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

4
@TranslucentCloud Oh yeah, các phương thức trợ giúp chắc chắn rất hay, nhưng tôi nghĩ mọi người nên biết những gì đang diễn ra dưới mui xe :-)
mpen

Tại sao chúng ta cần bước 2 và 3? Tại sao chúng ta không thể đột biến bản sao sau bước đầu tiên trực tiếp?
Evmorov

1
@Evmorov Bởi vì nó không phải là một bản sao sâu sắc. Bước 1 chỉ là nhân bản mảng, không phải các đối tượng bên trong. Nói cách khác, mỗi đối tượng bên trong mảng mới vẫn "trỏ đến" các đối tượng hiện có trong bộ nhớ - biến đổi một đối tượng sẽ biến đổi đối tượng khác (chúng là một và giống nhau). Xem thêm items[0] === clone[0]bit trong ví dụ thiết bị đầu cuối của tôi ở phía dưới. =Kiểm tra ba nếu các đối tượng đề cập đến cùng một điều.
mở

Trở nên phức tạp hơn nếu bạn không biết chỉ mục của mục trong mảng.
Ian Warburton

@IanWarburton Không thực sự. items.findIndex()nên làm công việc ngắn ngủi đó.
mở

92

Nhầm đường!

handleChange = (e) => {
    const { items } = this.state;
    items[1].name = e.target.value;

    // update state
    this.setState({
        items,
    });
};

Như được chỉ ra bởi rất nhiều nhà phát triển tốt hơn trong các ý kiến: đột biến nhà nước là sai!

Mất một lúc để tìm hiểu điều này. Ở trên hoạt động nhưng nó lấy đi sức mạnh của React. Ví dụ componentDidUpdatesẽ không xem đây là bản cập nhật vì nó được sửa đổi trực tiếp.

Vì vậy, cách đúng đắn sẽ là:

handleChange = (e) => {
    this.setState(prevState => ({
        items: {
            ...prevState.items,
            [prevState.items[1].name]: e.target.value,
        },
    }));
};

25
ES6 chỉ vì bạn đang sử dụng "const"?
nkkollaw

49
Không phải là gọi items[1].role = e.target.valuetrạng thái đột biến trực tiếp?
antony

28
bạn đang làm thay đổi trạng thái, điều này hoàn toàn chống lại ý tưởng duy trì trạng thái bất biến như phản ứng được đề xuất. Điều này có thể cung cấp cho bạn rất nhiều đau đớn trong một ứng dụng lớn.
ncubica

8
@MarvinVK, câu trả lời của bạn cho biết "Vì vậy, cách thực hành tốt nhất sẽ là:" tiếp theo là sử dụng "this.forceUpdate ();" không được khuyến nghị vì nó có thể được ghi đè bởi setState (), xem facebook.github.io/react/docs/react-component.html#state . Tốt nhất để thay đổi điều này để nó không gây nhầm lẫn cho độc giả trong tương lai.
James Z.

8
Chỉ muốn chỉ ra rằng rất nhiều ý kiến ​​không liên quan bây giờ do chỉnh sửa và đúng cách thực sự là đúng cách.
heez

50

Để sửa đổi các đối tượng / biến được lồng nhau sâu trong trạng thái của React, thông thường có ba phương thức được sử dụng: vanilla JavaScript Object.assign, trình trợ giúp bất biếncloneDeeptừ Lodash .

Ngoài ra còn có rất nhiều libs bên thứ ba ít phổ biến khác để đạt được điều này, nhưng trong câu trả lời này, tôi sẽ chỉ đề cập đến ba tùy chọn này. Ngoài ra, một số phương thức JavaScript vanilla bổ sung tồn tại, như trải rộng mảng, (ví dụ xem câu trả lời của @ mpen), nhưng chúng không trực quan, dễ sử dụng và có khả năng xử lý tất cả các tình huống thao tác trạng thái.

Như đã được chỉ ra vô số lần trong các bình luận được bình chọn hàng đầu cho các câu trả lời, mà các tác giả đề xuất một sự đột biến trực tiếp của nhà nước: chỉ cần không làm điều đó . Đây là một mô hình chống React phổ biến, chắc chắn sẽ dẫn đến hậu quả không mong muốn. Học đúng cách.

Hãy so sánh ba phương pháp được sử dụng rộng rãi.

Cho cấu trúc đối tượng trạng thái này:

state = {
    outer: {
        inner: 'initial value'
    }
}

Bạn có thể sử dụng các phương pháp sau để cập nhật innergiá trị của trường bên trong nhất mà không ảnh hưởng đến phần còn lại của trạng thái.

1. Object.assign của Vanilla JavaScript

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')
)
<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>

Hãy nhớ rằng Object.assign sẽ không thực hiện nhân bản sâu , vì nó chỉ sao chép các giá trị thuộc tính và đó là lý do tại sao cái được gọi là sao chép nông (xem bình luận).

Để làm việc này, chúng ta chỉ nên thao tác các thuộc tính của kiểu nguyên thủy ( outer.inner), đó là chuỗi, số, booleans.

Trong ví dụ này, chúng ta đang tạo một hằng số mới ( const newOuter...), bằng cách sử dụng Object.assign, tạo ra một đối tượng trống ( {}), sao chép outerđối tượng ( { inner: 'initial value' }) vào đó và sau đó sao chép một đối tượng khác { inner: 'updated value' } lên trên nó.

Bằng cách này, cuối cùng, newOuterhằng số mới được tạo sẽ giữ một giá trị { inner: 'updated value' }kể từ khi thuộc innertính bị ghi đè. Đây newOuterlà một đối tượng hoàn toàn mới, không được liên kết với đối tượng ở trạng thái, vì vậy nó có thể bị thay đổi khi cần thiết và trạng thái sẽ giữ nguyên và không thay đổi cho đến khi lệnh cập nhật được chạy.

Phần cuối cùng là sử dụng setOuter()setter để thay thế bản gốc outertrong trạng thái bằng một newOuterđối tượng mới được tạo (chỉ giá trị sẽ thay đổi, tên thuộc tính outersẽ không).

Bây giờ hãy tưởng tượng chúng ta có một trạng thái sâu sắc hơn như thế state = { outer: { inner: { innerMost: 'initial value' } } }. Chúng ta có thể cố gắng tạo newOuterđối tượng và điền vào outernội dung từ trạng thái, nhưng Object.assignsẽ không thể sao chép innerMostgiá trị của newOuterđối tượng mới được tạo này vì innerMostđược lồng quá sâu.

Bạn vẫn có thể sao chép inner, như trong ví dụ trên, nhưng vì bây giờ nó là một đối tượng và không phải là nguyên thủy, nên tham chiếu từ newOuter.innersẽ được sao chép vào outer.innerthay vào đó, điều đó có nghĩa là chúng ta sẽ kết thúc với newOuterđối tượng cục bộ trực tiếp gắn với đối tượng ở trạng thái .

Điều đó có nghĩa là trong trường hợp này các đột biến của cục bộ được tạo newOuter.innersẽ ảnh hưởng trực tiếp đến outer.innerđối tượng (ở trạng thái), vì thực tế chúng đã trở thành cùng một thứ (trong bộ nhớ của máy tính).

Object.assign do đó sẽ chỉ hoạt động nếu bạn có cấu trúc trạng thái sâu một cấp tương đối đơn giản với các thành viên trong cùng giữ các giá trị của kiểu nguyên thủy.

Nếu bạn có các đối tượng sâu hơn (cấp 2 trở lên), bạn nên cập nhật, không sử dụng Object.assign. Bạn có nguy cơ đột biến trạng thái trực tiếp.

2. Bản sao của 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')
)
<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>

ClashDeep của Lodash là cách đơn giản hơn để sử dụng. Nó thực hiện một nhân bản sâu , vì vậy nó là một tùy chọn mạnh mẽ, nếu bạn có một trạng thái khá phức tạp với các đối tượng đa cấp hoặc mảng bên trong. Chỉ cloneDeep()là tài sản nhà nước cấp cao nhất, biến đổi phần nhân bản theo bất cứ cách nào bạn muốn, và setOuter()nó trở lại trạng thái.

3. bất biến-người trợ giúp

const App = () => {
  const [outer, setOuter] = React.useState({ inner: 'initial value' })
  
  React.useEffect(() => {
    const update = immutabilityHelper
    console.log('Before the deep cloning and updating:', outer.inner) // initial value
    const newOuter = update(outer, { inner: { $set: 'updated value' } })
    console.log('After the cloning and updating, 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')
)
<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://wzrd.in/standalone/immutability-helper@3.0.0"></script>

<main id="react"></main>

immutability-helpermất nó vào một cấp độ hoàn toàn mới, và điều thú vị về nó là nó không chỉ có thể $setđánh giá cao đến các mục nhà nước, mà còn $push, $splice, $merge(vv) cho họ. Dưới đây là danh sách các lệnh có sẵn.

Ghi chú bên

Một lần nữa, hãy nhớ rằng, setOuterchỉ sửa đổi các thuộc tính cấp đầu tiên của đối tượng trạng thái ( outertrong các ví dụ này), chứ không phải lồng nhau sâu ( outer.inner). Nếu nó hành xử theo một cách khác, câu hỏi này sẽ không tồn tại.

Cái nào phù hợp với dự án của bạn?

Nếu bạn không muốn hoặc không thể sử dụng các phụ thuộc bên ngoài và có cấu trúc trạng thái đơn giản , hãy tuân thủ Object.assign.

Nếu bạn thao túng một trạng thái lớn và / hoặc phức tạp , thì Lodash cloneDeeplà một lựa chọn sáng suốt.

Nếu bạn cần các khả năng nâng cao , tức là nếu cấu trúc trạng thái của bạn phức tạp và bạn cần thực hiện tất cả các loại hoạt động trên nó, hãy thử immutability-helper, đó là một công cụ rất tiên tiến có thể được sử dụng để thao tác trạng thái.

... Hoặc, bạn có thực sự cần phải làm điều này không?

Nếu bạn giữ một dữ liệu phức tạp ở trạng thái React, có lẽ đây là thời điểm tốt để suy nghĩ về các cách xử lý khác. Đặt một đối tượng trạng thái phức tạp ngay trong các thành phần React không phải là một hoạt động đơn giản và tôi thực sự khuyên bạn nên suy nghĩ về các phương pháp khác nhau.

Rất có thể bạn nên tắt dữ liệu phức tạp của mình trong một cửa hàng Redux, đặt nó ở đó bằng cách sử dụng các bộ giảm tốc và / hoặc sagas và truy cập nó bằng các bộ chọn.


Object.assignkhông thực hiện một bản sao sâu. Nói, nếu a = {c: {d: 1}}b = Object.assign({}, a), sau đó bạn thực hiện b.c.d = 4, sau đó a.c.dbị đột biến.
Awol

Bạn nói đúng, giá trị 1của đối tượng trong cùng ( a.c.d) sẽ bị thay đổi. Nhưng nếu bạn sẽ chỉ định lại người kế vị cấp đầu tiên b, như thế này : b.c = {f: 1}, phần tương ứng asẽ không bị biến đổi (nó sẽ ở lại {d: 1}). Dù sao thì cũng rất tuyệt, tôi sẽ cập nhật câu trả lời ngay lập tức.
Chất dẫn truyền thần kinh

Những gì bạn đã xác định thực sự là một shallow copyvà không phải là a deep copy. Thật dễ dàng để nhầm lẫn những gì shallow copycó nghĩa là. Trong shallow copy, a !== bnhưng đối với mỗi khóa từ đối tượng nguồn a,a[key] === b[key]
Awol

Vâng, được đề cập rõ ràng về sự nông cạn Object.assigntrong câu trả lời.
Chất dẫn truyền thần kinh

JSON.parse(JSON.stringify(object))cũng là một biến thể cho bản sao sâu. Hiệu suất là tồi tệ hơn sau đó lodash cloneDeepmặc dù. measurethat.net/Benchmarks/Show/2751/0/...
tylik

35

Tôi đã từng gặp vấn đề tương tự. Đây là một giải pháp đơn giản mà hiệu quả!

const newItems = [...this.state.items];
newItems[item] = value;
this.setState({ items:newItems });

11
@TranslucentCloud - đây chắc chắn không phải là đột biến trực tiếp. mảng ban đầu được sao chép, sửa đổi và sau đó trạng thái được thiết lập lại bằng cách sử dụng Mảng nhân bản.
vsync

@vsync yeah, bây giờ sau khi bạn chỉnh sửa câu trả lời ban đầu thì đây không phải là đột biến.
Chất dẫn truyền thần kinh

2
@TranslucentCloud - Trước đây, bản chỉnh sửa của tôi không liên quan gì đến nó, cảm ơn rất nhiều về thái độ. @Jonas ở đây chỉ mắc một lỗi đơn giản trong câu trả lời của anh ấy, sử dụng {dấu ngoặc nhọn thay vì dấu ngoặc mà tôi đã khắc phục
vsync

31

Theo tài liệu React trên setState , sử dụng Object.assigntheo gợi ý của các câu trả lời khác ở đây là không lý tưởng. Do bản chất của setStatehành vi không đồng bộ, các cuộc gọi tiếp theo sử dụng kỹ thuật này có thể ghi đè các cuộc gọi trước đó gây ra kết quả không mong muốn.

Thay vào đó, các tài liệu React khuyên bạn nên sử dụng hình thức cập nhật setStatehoạt động ở trạng thái trước đó. Hãy nhớ rằng khi cập nhật một mảng hoặc đối tượng, bạn phải trả về một mảng hoặc đối tượng mới vì React yêu cầu chúng ta duy trì trạng thái bất biến. Sử dụng toán tử trải rộng của cú pháp ES6 để sao chép một mảng nông, tạo hoặc cập nhật một thuộc tính của một đối tượng tại một chỉ mục nhất định của mảng sẽ như thế này:

this.setState(prevState => {
    const newItems = [...prevState.items];
    newItems[index].name = newName;
    return {items: newItems};
})

2
Đây là câu trả lời thích hợp nếu bạn sử dụng ES6. Đó là nội tuyến những gì @Jonas đã trả lời. Tuy nhiên do giải thích nó nổi bật.
Sourabh

Có, mã này đang hoạt động rất tốt và rất đơn giản ..
Shoeb Mirza

29

Trước tiên hãy lấy mục bạn muốn, thay đổi những gì bạn muốn trên đối tượng đó và đặt nó trở lại trạng thái. Cách bạn đang sử dụng trạng thái bằng cách chỉ truyền một đối tượng vào getInitialStatesẽ dễ dàng hơn nếu bạn sử dụng một đối tượng có khóa.

handleChange: function (e) {
   item = this.state.items[1];
   item.name = 'newName';
   items[1] = item;

   this.setState({items: items});
}

3
Không, nó mang lại Uncaught TypeError: Cannot read property 'items' of null.
martins

Không, nó sẽ không. Lỗi của bạn rất có thể bắt nguồn từ cách bạn đang làm getInitialState.
Henrik Andersson

10
@HenrikAndersson Một cái gì đó dường như không đúng trong ví dụ của bạn. itemskhông được định nghĩa ở bất cứ đâu.
Edward D'Souza

1
@ EdwardD'Souza Bạn hoàn toàn chính xác! Câu trả lời của tôi là chỉ ra cách xác định và sử dụng nó. Cách mã người hỏi được thiết lập không hoạt động cho điều anh ấy / cô ấy muốn, đó là lý do tại sao cần phải có một đối tượng khóa.
Henrik Andersson

7
Đây là kiểu chống React điển hình, bạn đang gán itemtham chiếu cho this.state.items[1]biến riêng của trạng thái . Sau đó, bạn sửa đổi item( item.name = 'newName'), và do đó đột biến trạng thái trực tiếp, điều này rất nản lòng. Trong ví dụ của bạn, thậm chí không cần phải gọi this.setState({items: items}), vì trạng thái đã bị đột biến trực tiếp.
Chất dẫn truyền thần kinh

22

Đừng thay đổi trạng thái tại chỗ. Nó có thể gây ra kết quả bất ngờ. Tôi đã học được bài học của tôi! Luôn luôn làm việc với một bản sao / bản sao, Object.assign()là một điều tốt:

item = Object.assign({}, this.state.items[1], {name: 'newName'});
items[1] = item;
this.setState({items: items});

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign


8
những gì là itemstrong ví dụ của bạn? Ý của bạn là this.state.itemsgì hay cái gì khác?
Buh Buh

Tôi đã thử nghiệm điều này nhưng anh ta đang thiếu một dòng. Ở trên items[1] = item;nên có một dòng nói items = this.state.items;. Coi chừng javascript của tôi bị gỉ và tôi đang học phản ứng cho dự án nhà của tôi vì vậy tôi không biết điều này tốt hay xấu :-)
Greg0ry

4

Nó thực sự đơn giản.

Đầu tiên kéo toàn bộ đối tượng vật phẩm từ trạng thái, cập nhật một phần của đối tượng vật phẩm như mong muốn và đưa toàn bộ đối tượng vật phẩm trở lại trạng thái thông qua setState.

handleChange: function (e) {
  items = Object.assign(this.state.items); // Pull the entire items object out. Using object.assign is a good idea for objects.
  items[1].name = 'newName'; // update the items object as needed
  this.setState({ items }); // Put back in state
}

"Object.assign sẽ hoạt động nếu bạn có cấu trúc trạng thái sâu một cấp tương đối đơn giản với các thành viên trong cùng nắm giữ các giá trị của kiểu nguyên thủy".
Chất dẫn truyền thần kinh

3

Vì không có tùy chọn nào ở trên là lý tưởng đối với tôi nên cuối cùng tôi đã sử dụng bản đồ:

this.setState({items: this.state.items.map((item,idx)=> idx!==1 ?item :{...item,name:'new_name'}) })

2

Đột biến miễn phí:

// given a state
state = {items: [{name: 'Fred', value: 1}, {name: 'Wilma', value: 2}]}

// This will work without mutation as it clones the modified item in the map:
this.state.items
   .map(item => item.name === 'Fred' ? {...item, ...{value: 3}} : item)

this.setState(newItems)

2
Tôi không thể thấy nơi newItemsđược đặt.
Chất dẫn truyền thần kinh

Không phải là một bản đồ cộng với so sánh trong mảng khủng khiếp cho hiệu suất?
Natassia Tavares

@NatassiaTavares .. gì? có thể bạn đang nhầm lẫn với fo ofhoặc forEachbản đồ là nhanh nhất.
Deano

1

Tìm thấy điều này đáng ngạc nhiên khó khăn và không có phép thuật lây lan ES6 nào có vẻ hoạt động như mong đợi. Đã sử dụng một cấu trúc như thế này để có được các thuộc tính phần tử được kết xuất cho mục đích bố trí.

được tìm thấy bằng cách sử dụng updatephương thức immutability-helperđể trở thành một phương pháp đơn giản nhất trong ví dụ đơn giản này:

constructor(props) {
    super(props)
    this.state = { values: [] }
    this.updateContainerState = this.updateContainerState.bind(this)
  }

updateContainerState(index, value) {
    this.setState((state) => update(state, { values: { [index]: { $set: value } } }))
  }

như được điều chỉnh từ https://github.com/kolodny/immutability-helper#computing-property-names

của thành viên mảng được cập nhật là một đối tượng phức tạp lồng nhau hơn, sử dụng phương thức sao chép sâu thích hợp dựa trên độ phức tạp.

Chắc chắn có nhiều cách tốt hơn để xử lý các tham số bố trí, nhưng đây là về cách xử lý mảng. Các giá trị có liên quan cho từng phần tử con cũng có thể được tính bên ngoài chúng, nhưng tôi thấy việc chuyển containerState xuống thuận tiện hơn, vì vậy chúng có thể tìm nạp các thuộc tính theo ý muốn và Cập nhật mảng trạng thái cha mẹ theo chỉ mục đã cho.

import React from 'react'
import update from 'immutability-helper'
import { ContainerElement } from './container.component.style.js'
import ChildComponent from './child-component'
export default class ContainerComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { values: [] }
    this.updateContainerState = this.updateContainerState.bind(this)
  }

  updateContainerState(index, value) {
    this.setState((state) => update(state, { values: { [index]: { $set: value } } }))
  }

  // ...

  render() {
    let index = 0
    return (
      <ContainerElement>
      <ChildComponent
        index={index++}
        containerState={this.state}
        updateContainerState={this.updateContainerState}
      />
      <ChildComponent
        index={index++}
        containerState={this.state}
        updateContainerState={this.updateContainerState}
      />
      </ContainerElement>
    )
  }
}

1

Sử dụng bản đồ mảng với chức năng mũi tên, trong một dòng

this.setState({
    items: this.state.items.map((item, index) =>
      index === 1 ? { ...item, name: 'newName' } : item,
   )
})

1
Điều này về cơ bản là giống hệt với câu trả lời khác được đăng khoảng một năm trước
SurePerformance

0

Sử dụng sự kiện trên handleChangeđể tìm ra yếu tố đã thay đổi và sau đó cập nhật nó. Cho rằng bạn có thể cần phải thay đổi một số thuộc tính để xác định nó và cập nhật nó.

Xem fiddle https://jsfiddle.net/69z2wepo/6164/


0

Tôi sẽ di chuyển thay đổi hàm xử lý và thêm một tham số chỉ mục

handleChange: function (index) {
    var items = this.state.items;
    items[index].name = 'newName';
    this.setState({items: items});
},

đến thành phần biểu mẫu động và chuyển nó đến thành phần PopulationAtCheckboxes dưới dạng prop. Khi bạn lặp lại các mục của mình, bạn có thể bao gồm một bộ đếm bổ sung (được gọi là chỉ mục trong mã bên dưới) để được chuyển qua thay đổi xử lý như hiển thị bên dưới

{ Object.keys(this.state.items).map(function (key, index) {
var item = _this.state.items[key];
var boundHandleChange = _this.handleChange.bind(_this, index);
  return (
    <div>
        <PopulateAtCheckboxes this={this}
            checked={item.populate_at} id={key} 
            handleChange={boundHandleChange}
            populate_at={data.populate_at} />
    </div>
);
}, this)}

Cuối cùng, bạn có thể gọi trình lắng nghe thay đổi của bạn như hiển thị bên dưới đây

<input type="radio" name={'populate_at'+this.props.id} value={value} onChange={this.props.handleChange} checked={this.props.checked == value} ref="populate-at"/>

Không bao giờ thay đổi trạng thái của React trực tiếp.
Chất dẫn truyền thần kinh

0

Nếu bạn chỉ cần thay đổi một phần của Array, Bạn đã là thành phần phản ứng với trạng thái được đặt thành.

state = {items: [{name: 'red-one', value: 100}, {name: 'green-one', value: 999}]}

Tốt nhất là nên cập nhật red-onetrong Arraynhư sau:

const itemIndex = this.state.items.findIndex(i=> i.name === 'red-one');
const newItems = [
   this.state.items.slice(0, itemIndex),
   {name: 'red-one', value: 666},
   this.state.items.slice(itemIndex)
]

this.setState(newItems)

newArraygì ý bạn là newItemssao Nếu bạn làm thế, bạn sẽ không rời khỏi bang chỉ với một mặt hàng chứ?
micnil

Điều này sẽ giới thiệu một thuộc tính mới newItemscho stateđối tượng và sẽ không cập nhật thuộc tính hiện có items.
Chất dẫn truyền thần kinh

0

hoặc nếu bạn có một danh sách được tạo động và bạn không biết chỉ mục mà chỉ có khóa hoặc id:

let ItemsCopy = []
let x = this.state.Items.map((entry) =>{

    if(entry.id == 'theIDYoureLookingFor')
    {
        entry.PropertyToChange = 'NewProperty'
    }

    ItemsCopy.push(entry)
})


this.setState({Items:ItemsCopy});

0

Thử với mã:

this.state.items[1] = 'new value';
var cloneObj = Object.assign({}, this.state.items);

this.setState({items: cloneObj });

0

Theo sau đoạn mã đi dễ dàng trên bộ não buồn tẻ của tôi. Xóa đối tượng và thay thế bằng đối tượng được cập nhật

    var udpateditem = this.state.items.find(function(item) { 
                   return item.name == "field_1" });
    udpateditem.name= "New updated name"                       
    this.setState(prevState => ({                                   
    items:prevState.dl_name_template.filter(function(item) { 
                                    return item.name !== "field_1"}).concat(udpateditem)
    }));

0

Làm thế nào về việc tạo một thành phần khác (cho đối tượng cần đi vào mảng) và chuyển các mục sau đây dưới dạng đạo cụ?

  1. chỉ số thành phần - chỉ mục sẽ được sử dụng để tạo / cập nhật trong mảng.
  2. set function - Hàm này đưa dữ liệu vào mảng dựa trên chỉ số thành phần.
<SubObjectForm setData={this.setSubObjectData}                                                            objectIndex={index}/>

Ở đây {index} có thể được chuyển qua dựa trên vị trí sử dụng SubObjectForm này.

và setSubObjectData có thể giống như thế này.

 setSubObjectData: function(index, data){
      var arrayFromParentObject= <retrieve from props or state>;
      var objectInArray= arrayFromParentObject.array[index];
      arrayFromParentObject.array[index] = Object.assign(objectInArray, data);
 }

Trong SubObjectForm, this.props.setData có thể được gọi khi thay đổi dữ liệu như được đưa ra dưới đây.

<input type="text" name="name" onChange={(e) => this.props.setData(this.props.objectIndex,{name: e.target.value})}/>

0
this.setState({
      items: this.state.items.map((item,index) => {
        if (index === 1) {
          item.name = 'newName';
        }
        return item;
      })
    });

1
Điều này là không tối ưu, bạn lặp lại toàn bộ mảng để chỉ cập nhật mục thứ hai?
wscourge

Bạn cũng đang thay đổi thành phần trong mảng đầu tiên, bạn nên sử dụngitem = Object.assign({}, item, {name: 'newName'});
remram

0

Câu trả lời của @ JonnyIDIAanan hoạt động hoàn hảo, nhưng chỉ với biến trạng thái mảng. Trong trường hợp biến trạng thái chỉ là một từ điển duy nhất, hãy làm theo điều này:

inputChange = input => e => {
    this.setState({
        item: update(this.state.item, {[input]: {$set: e.target.value}})
    })
}

Bạn có thể thay thế [input]bằng tên trường của từ điển của bạn và e.target.valuetheo giá trị của nó. Mã này thực hiện công việc cập nhật về sự kiện thay đổi đầu vào của biểu mẫu của tôi.


-3

Hãy thử nó sẽ chắc chắn hoạt động, trường hợp khác tôi đã thử nhưng không hoạt động

import _ from 'lodash';

this.state.var_name  = _.assign(this.state.var_name, {
   obj_prop: 'changed_value',
});

Không bao giờ thay đổi trạng thái của React trực tiếp.
Chất dẫn truyền thần kinh

-3
 handleChanges = (value, key) => {
     // clone the current State object
    let cloneObject = _.extend({}, this.state.currentAttribute);
    // key as user.name and value= "ABC" then current attributes have current properties as we changes
    currentAttribute[key] = value;
    // then set the state "currentAttribute" is key and "cloneObject" is changed object.  
    this.setState({currentAttribute: cloneObject});

và Thay đổi từ hộp văn bản thêm sự kiện onChange

onChange = {
   (event) => {                                                
      this.handleChanges(event.target.value, "title");
   }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.