Cách cập nhật các thuộc tính trạng thái lồng nhau trong React


390

Tôi đang cố gắng tổ chức trạng thái của mình bằng cách sử dụng thuộc tính lồng nhau như thế này:

this.state = {
   someProperty: {
      flag:true
   }
}

Nhưng cập nhật trạng thái như thế này,

this.setState({ someProperty.flag: false });

không hoạt động. Làm thế nào điều này có thể được thực hiện chính xác?


4
Ý bạn là gì không làm việc? Đây không phải là một câu hỏi hay - chuyện gì đã xảy ra? Có lỗi gì không? Lỗi gì?
Darren Sweeney


16
Câu trả lời là Đừng sử dụng Trạng thái lồng nhau trong React . Đọc câu trả lời tuyệt vời này .
Qwerty

4
Nên dùng cái gì thay thế?
Jāni Elmeris 17/2/19

42
Đơn giản là không sử dụng trạng thái lồng nhau là một câu trả lời không thể chấp nhận được cho ngày nay React được sử dụng rộng rãi như thế nào. Tình huống này sẽ xảy ra và các nhà phát triển cần một câu trả lời cho điều này.
huyết thanh

Câu trả lời:


455

Để setStatecho một đối tượng lồng nhau, bạn có thể làm theo cách tiếp cận dưới đây vì tôi nghĩ setState không xử lý các cập nhật lồng nhau.

var someProperty = {...this.state.someProperty}
someProperty.flag = true;
this.setState({someProperty})

Ý tưởng là tạo ra một đối tượng giả thực hiện các thao tác trên nó và sau đó thay thế trạng thái của thành phần bằng đối tượng được cập nhật

Bây giờ, toán tử trải chỉ tạo một bản sao lồng nhau của đối tượng. Nếu trạng thái của bạn được lồng cao như:

this.state = {
   someProperty: {
      someOtherProperty: {
          anotherProperty: {
             flag: true
          }
          ..
      }
      ...
   }
   ...
}

Bạn có thể setState bằng cách sử dụng toán tử trải rộng ở mỗi cấp độ như

this.setState(prevState => ({
    ...prevState,
    someProperty: {
        ...prevState.someProperty,
        someOtherProperty: {
            ...prevState.someProperty.someOtherProperty, 
            anotherProperty: {
               ...prevState.someProperty.someOtherProperty.anotherProperty,
               flag: false
            }
        }
    }
}))

Tuy nhiên, cú pháp trên trở nên xấu hơn khi trạng thái ngày càng được lồng vào nhau và do đó tôi khuyên bạn nên sử dụng immutability-helper gói để cập nhật trạng thái.

Xem câu trả lời này về cách cập nhật trạng thái với immutability helper.


2
@Ohgodwhy, không có này không truy cập vào nhà nước trực tiếp từ {...} this.state.someProperty retuns một obejct mới somePropertyvà chúng tôi đang thay đổi đó
Shubham Khatri

4
điều này không hiệu quả với tôi .. tôi đang sử dụng phiên bản phản ứng @ 15.6.1
Rajiv

5
@Stophface Chúng tôi có thể sử dụng Lodash để sao chép sâu, nhưng không phải ai cũng bao gồm thư viện này chỉ để setState
Shubham Khatri

15
Điều này có vi phạm Reacjs.org/docs/ không? Nếu hai thay đổi đối tượng được lồng vào nhau, thay đổi cuối cùng sẽ ghi đè lên đối tượng đầu tiên. Vì vậy, cách chính xác nhất sẽ là: this.setState((prevState) => ({nested: {...prevState.nested, propertyToSet: newValue}}) Một chút tẻ nhạt mặc dù ...
olejorgenb

2
Tôi không nghĩ rằng bạn cần ...prevState,ví dụ cuối cùng, chỉ bạn somePropertyvà con của nó
Jemar Jones

140

Để viết nó trong một dòng

this.setState({ someProperty: { ...this.state.someProperty, flag: false} });

12
Bạn nên sử dụng trình cập nhật cho setState thay vì truy cập trực tiếp this.state trong setState. Reacjs.org/docs/react-component.html#setstate
Raghu Teja

6
Tôi tin rằng đó là câu nói, đừng đọc this.statengay sau đó setStatevà hy vọng nó sẽ có giá trị mới. Versus gọi this.statetrong setState. Hoặc cũng có một vấn đề với cái sau không rõ ràng với tôi?
trpt4him

2
Như trpt4him đã nói, liên kết bạn đã nói về vấn đề truy cập this.statesau setState. Bên cạnh đó, chúng tôi không chuyển this.stateđến setState. Các thuộc tính trải rộng toán tử. Nó giống như Object.assign (). github.com/tc39/projection-object-rest-s
lây lan

3
@RaghuTeja có lẽ chỉ cần làm this.setState(currentState => { someProperty: { ...currentState.someProperty, flag: false} });có thể cho phép tránh vấn đề này?
Nữ web

Tôi đã thêm câu trả lời làm thế nào để đạt được điều tương tự với hook useState cho đầy đủ.
Andrew

90

Đôi khi câu trả lời trực tiếp không phải là câu trả lời hay nhất :)

Phiên bản ngắn:

mã này

this.state = {
    someProperty: {
        flag: true
    }
}

nên được đơn giản hóa như một cái gì đó như

this.state = {
    somePropertyFlag: true
}

Phiên bản dài:

Hiện tại bạn không muốn làm việc với trạng thái lồng nhau trong React . Bởi vì React không được định hướng để làm việc với các trạng thái lồng nhau và tất cả các giải pháp được đề xuất ở đây trông giống như hack. Họ không sử dụng khuôn khổ mà chiến đấu với nó. Họ đề nghị viết mã không quá rõ ràng cho mục đích nghi ngờ của việc nhóm một số thuộc tính. Vì vậy, họ rất thú vị như một câu trả lời cho thử thách nhưng thực tế là vô dụng.

Hãy hình dung trạng thái sau:

{
    parent: {
        child1: 'value 1',
        child2: 'value 2',
        ...
        child100: 'value 100'
    }
}

Điều gì sẽ xảy ra nếu bạn thay đổi chỉ là một giá trị child1? React sẽ không hiển thị lại chế độ xem vì nó sử dụng so sánh nông và nó sẽ thấy rằngparent tính không thay đổi. BTW đột biến đối tượng trạng thái trực tiếp được coi là một thực tiễn xấu nói chung.

Vì vậy, bạn cần phải tạo lại toàn bộ parent đối tượng. Nhưng trong trường hợp này chúng ta sẽ gặp một vấn đề khác. React sẽ nghĩ rằng tất cả trẻ em đã thay đổi giá trị của chúng và sẽ kết xuất lại tất cả chúng. Tất nhiên nó không tốt cho hiệu suất.

Vẫn có thể giải quyết vấn đề đó bằng cách viết một số logic phức tạp shouldComponentUpdate()nhưng tôi muốn dừng lại ở đây và sử dụng giải pháp đơn giản từ phiên bản ngắn.


4
Tôi nghĩ rằng có những trường hợp sử dụng trong đó một trạng thái lồng nhau là hoàn toàn tốt. Ví dụ, một trạng thái theo dõi động các cặp khóa-giá trị. Xem ở đây làm thế nào bạn có thể cập nhật trạng thái cho chỉ một mục trong trạng thái: Reacjs.org/docs/iêu
pors

1
So sánh nông được sử dụng cho các thành phần thuần theo mặc định.
Basel Shishani

4
Tôi muốn tạo ra một bàn thờ để vinh danh bạn và cầu nguyện cho nó mỗi sáng. Tôi đã mất ba ngày để đạt được câu trả lời này giải thích hoàn hảo quyết định thiết kế OBVIOUS. Mọi người chỉ cố gắng sử dụng toán tử lây lan hoặc thực hiện các hack khác chỉ vì trạng thái lồng nhau trông dễ đọc hơn.
Edeph

1
Làm thế nào để bạn đề xuất sau đó, sử dụng React để thực hiện những việc như kết xuất cây tương tác (ví dụ: công ty của tôi có một loạt các cảm biến ở các nơi khác nhau trên thế giới, mỗi loại khác nhau, với các thuộc tính khác nhau, ở các vị trí khác nhau (80+ ) và KHÓA HỌC, chúng được tổ chức theo một hệ thống phân cấp vì mỗi lần triển khai tốn hàng ngàn đô la và là một dự án hoàn chỉnh nên không thể quản lý chúng nếu không có hệ thống phân cấp). Tôi khá tò mò về cách bạn sẽ không mô hình hóa những thứ như cây như ... cây, trong React.
DanyAlejandro

10
có vẻ như React không được tạo ra để đại diện cho bất cứ thứ gì được lưu trữ dưới dạng cấu trúc đệ quy của độ sâu không tầm thường. Thật đáng thất vọng khi chế độ xem dạng cây xuất hiện trong hầu hết mọi ứng dụng phức tạp ngoài kia (gmail, trình quản lý tệp, bất kỳ hệ thống quản lý tài liệu nào, ..). Tôi đã sử dụng React cho những thứ này, nhưng luôn có lòng nhiệt tình React nói với mọi người rằng bạn đang thực hiện các mô hình chống và đề xuất giải pháp là giữ các bản sao sâu của cây ở trạng thái của mỗi nút (có, 80 bản sao). Tôi rất muốn thấy một thành phần xem cây không hack mà không phá vỡ bất kỳ quy tắc React nào.
DanyAlejandro

61

Khước từ

Nested State trong React là thiết kế sai

Đọc câu trả lời tuyệt vời này .

 

Lý do đằng sau câu trả lời này:

SetState của React chỉ là một tiện ích tích hợp, nhưng bạn sẽ sớm nhận ra rằng nó có giới hạn của nó. Sử dụng các thuộc tính tùy chỉnh và sử dụng thông minh của ForceUpdate mang lại cho bạn nhiều hơn nữa. ví dụ:

class MyClass extends React.Component {
    myState = someObject
    inputValue = 42
...

MobX, ví dụ, bỏ hoàn toàn trạng thái và sử dụng các thuộc tính có thể quan sát tùy chỉnh.
Sử dụng Observables thay vì trạng thái trong các thành phần React.

 


câu trả lời cho sự khốn khổ của bạn - xem ví dụ ở đây

Có một cách khác ngắn hơn để cập nhật bất cứ tài sản lồng nhau nào.

this.setState(state => {
  state.nested.flag = false
  state.another.deep.prop = true
  return state
})

Trên một dòng

 this.setState(state => (state.nested.flag = false, state))

lưu ý: Đây là toán tử dấu phẩy ~ MDN , xem nó hoạt động ở đây (Hộp cát) .

Nó tương tự như (mặc dù điều này không thay đổi tham chiếu trạng thái)

this.state.nested.flag = false
this.forceUpdate()

Đối với sự khác biệt tinh tế trong bối cảnh này giữa forceUpdatesetStatexem ví dụ được liên kết.

Tất nhiên điều này là lạm dụng một số nguyên tắc cốt lõi, vì statechỉ nên đọc, nhưng vì bạn ngay lập tức loại bỏ trạng thái cũ và thay thế nó bằng trạng thái mới, nó hoàn toàn ổn.

Cảnh báo

Mặc dù thành phần có chứa trạng thái sẽ cập nhật và đăng ký lại đúng cách ( ngoại trừ gotcha này ) , các đạo cụ sẽ không truyền được cho trẻ em (xem bình luận của Spymaster bên dưới) . Chỉ sử dụng kỹ thuật này nếu bạn biết những gì bạn đang làm.

Ví dụ, bạn có thể vượt qua một chỗ dựa phẳng thay đổi được cập nhật và vượt qua dễ dàng.

render(
  //some complex render with your nested state
  <ChildComponent complexNestedProp={this.state.nested} pleaseRerender={Math.random()}/>
)

Bây giờ mặc dù tham chiếu cho ComplexNestedProp không thay đổi ( ShouldComponentUpdate )

this.props.complexNestedProp === nextProps.complexNestedProp

thành phần sẽ chạy lại bất cứ khi nào cập nhật thành phần cha mẹ, đó là trường hợp sau khi gọi this.setStatehoặc this.forceUpdatetrong cha mẹ.

Ảnh hưởng của đột biến nhà nước

Sử dụng nhà nước lồng nhau và biến đổi trạng thái trực tiếp rất nguy hiểm vì đối tượng khác nhau có thể giữ (cố ý hay không) (cũ) tài liệu tham khảo khác nhau đối với nhà nước và có thể không nhất thiết phải biết khi nào nên cập nhật (ví dụ như khi sử dụng PureComponenthoặc nếu shouldComponentUpdateđược thực hiện để trả lại false) HOẶC là dự định hiển thị dữ liệu cũ như trong ví dụ dưới đây.

Tưởng tượng một dòng thời gian được cho là hiển thị dữ liệu lịch sử, việc thay đổi dữ liệu dưới tay sẽ dẫn đến hành vi không mong muốn vì nó cũng sẽ thay đổi các mục trước đó.

dòng chảy nhà nước dòng chảy nhà nước lồng nhau

Dù sao ở đây bạn có thể thấy rằng Nested PureChildClassnó không được đăng ký lại do đạo cụ không truyền bá.


12
Bạn không nên biến đổi bất kỳ trạng thái phản ứng bất cứ nơi nào. Nếu có các thành phần lồng nhau sử dụng dữ liệu từ state.nested hoặc state.another, chúng sẽ không kết xuất lại cũng như con của chúng. Điều này là do đối tượng không thay đổi, do đó React nghĩ rằng không có gì thay đổi.
Zeragamba

@ SpyMaster356 Tôi đã cập nhật câu trả lời của mình để giải quyết vấn đề này. Cũng lưu ý rằng MobX, ví dụ, bỏ statehoàn toàn và sử dụng các thuộc tính có thể quan sát tùy chỉnh . React's setStatechỉ là một tiện ích tích hợp, nhưng bạn sẽ sớm nhận ra rằng nó có giới hạn của nó. Sử dụng các thuộc tính tùy chỉnh và sử dụng thông minh forceUpdatecung cấp cho bạn nhiều hơn nữa. Sử dụng Observables thay vì trạng thái trong các thành phần React.
Qwerty

Ngoài ra tôi đã thêm một ví dụ Reac-experiment.herokuapp.com/state-flow (liên kết đến git ở trên cùng)
Qwerty

Điều gì xảy ra nếu bạn tải ví dụ một đối tượng có thuộc tính lồng nhau từ cơ sở dữ liệu vào trạng thái? Làm thế nào để tránh trạng thái lồng nhau trong trường hợp đó?
tobiv

3
@tobiv Tốt hơn là chuyển đổi dữ liệu khi tìm nạp vào một cấu trúc khác, nhưng nó phụ thuộc vào usecase. Bạn có thể có một đối tượng lồng nhau hoặc một mảng các đối tượng ở trạng thái nếu bạn nghĩ chúng là các đơn vị nhỏ gọn của một số dữ liệu. Vấn đề phát sinh khi bạn cần lưu trữ các bộ chuyển đổi UI hoặc dữ liệu quan sát khác (tức là dữ liệu sẽ ngay lập tức thay đổi chế độ xem) vì chúng phải bằng phẳng để kích hoạt chính xác các thuật toán khuếch tán React bên trong. Đối với các thay đổi trong các đối tượng lồng nhau khác, thật dễ dàng để kích hoạt this.forceUpdate()hoặc triển khai cụ thể shouldComponentUpdate.
Qwerty

21

Nếu bạn đang sử dụng ES2015, bạn có quyền truy cập vào Object.assign. Bạn có thể sử dụng nó như sau để cập nhật một đối tượng lồng nhau.

this.setState({
  someProperty: Object.assign({}, this.state.someProperty, {flag: false})
});

Bạn hợp nhất các thuộc tính được cập nhật với hiện có và sử dụng đối tượng được trả về để cập nhật trạng thái.

Chỉnh sửa: Đã thêm một đối tượng trống làm mục tiêu cho hàm gán để đảm bảo trạng thái không bị đột biến trực tiếp như carkod đã chỉ ra.


12
Và tôi có thể sai nhưng tôi không nghĩ bạn đang sử dụng React mà không có ES2015.
Madbreaks

16
const newState = Object.assign({}, this.state);
newState.property.nestedProperty = "new value";
this.setState(newState);

1
có vẻ như là giải pháp tao nhã hơn ở đây, 1) tránh sự lây lan tẻ nhạt bên trong setState và 2) đặt trạng thái mới bên trong setState, tránh làm biến đổi trực tiếp bên trong setState. Cuối cùng nhưng không kém 3) tránh việc sử dụng bất kỳ thư viện nào chỉ cho một nhiệm vụ đơn giản
Webdess

Hai ví dụ không tương đương. Nếu propertychứa một số thuộc tính lồng nhau, cái đầu tiên sẽ xóa chúng, cái thứ hai sẽ không. Codeandbox.io/s/namless-pine-42thu
Qwerty

Qwerty, bạn đã đúng, cảm ơn vì nỗ lực. Tôi đã gỡ bỏ phiên bản ES6.
Eyecandy.dev

Straighforward và đơn giản!
Tony Sepia

Tôi đã sử dụng giải pháp của bạn. Obj tiểu bang của tôi là nhỏ, và nó hoạt động tốt. React sẽ chỉ hiển thị cho trạng thái đã thay đổi hay nó sẽ kết xuất mọi thứ?
Kenji Noguchi

14

Có rất nhiều thư viện để giúp với điều này. Ví dụ: sử dụng bất biến-helper :

import update from 'immutability-helper';

const newState = update(this.state, {
  someProperty: {flag: {$set: false}},
};
this.setState(newState);

Sử dụng bộ lodash / fp :

import {set} from 'lodash/fp';

const newState = set(["someProperty", "flag"], false, this.state);

Sử dụng kết hợp lodash / fp :

import {merge} from 'lodash/fp';

const newState = merge(this.state, {
  someProperty: {flag: false},
});

Nếu bạn sử dụng lodash, có lẽ bạn muốn thử _.cloneDeepvà đặt trạng thái thành bản sao nhân bản.
Yi Liu

@YatteLiu: Tôi đã cập nhật câu trả lời để sử dụng lodash/fp, vì vậy chúng tôi không phải lo lắng về đột biến.
tokland

Bất kỳ lợi thế để lựa chọn mergehoặc setchức năng trong lodash / fp bên cạnh cú pháp?
Toivo Säwén

Câu trả lời tốt, có thể bạn muốn thêm rằng bạn phải gọi this.setState(newState)cho các phương thức lodash / fp là tốt. Một vấn đề khác là lodash .set(không phải fp) có một thứ tự các đối số khác nhau khiến tôi vấp ngã một lúc.
Toivo Säwén

7

Chúng tôi sử dụng Immer https://github.com/mweststrate/immer để xử lý các loại vấn đề này.

Chỉ cần thay thế mã này trong một trong các thành phần của chúng tôi

this.setState(prevState => ({
   ...prevState,
        preferences: {
            ...prevState.preferences,
            [key]: newValue
        }
}));

Với cái này

import produce from 'immer';

this.setState(produce(draft => {
    draft.preferences[key] = newValue;
}));

Với immer, bạn xử lý trạng thái của bạn như là một "đối tượng bình thường". Phép thuật xảy ra đằng sau hiện trường với các đối tượng proxy.


6

Đây là một biến thể của câu trả lời đầu tiên được đưa ra trong chuỗi này không yêu cầu bất kỳ gói, thư viện hoặc chức năng đặc biệt nào.

state = {
  someProperty: {
    flag: 'string'
  }
}

handleChange = (value) => {
  const newState = {...this.state.someProperty, flag: value}
  this.setState({ someProperty: newState })
}

Để đặt trạng thái của một trường lồng nhau cụ thể, bạn đã đặt toàn bộ đối tượng. Tôi đã làm điều này bằng cách tạo một biến newStatevà truyền bá nội dung của trạng thái hiện tại vào nó trước bằng cách sử dụng toán tử trải ES2015 . Sau đó, tôi thay thế giá trị this.state.flagbằng giá trị mới (vì tôi đã đặt flag: value sau khi tôi trải trạng thái hiện tại vào đối tượng, flagtrường trong trạng thái hiện tại bị ghi đè). Sau đó, tôi chỉ cần đặt trạng thái somePropertycho newStateđối tượng của mình .


6

Mặc dù lồng nhau không thực sự là cách bạn nên xử lý trạng thái thành phần, đôi khi vì một cái gì đó dễ dàng cho việc lồng đơn lớp.

Đối với một nhà nước như thế này

state = {
 contact: {
  phone: '888-888-8888',
  email: 'test@test.com'
 }
 address: {
  street:''
 },
 occupation: {
 }
}

Một phương pháp có thể sử dụng lại mà tôi đã sử dụng sẽ trông như thế này.

handleChange = (obj) => e => {
  let x = this.state[obj];
  x[e.target.name] = e.target.value;
  this.setState({ [obj]: x });
};

sau đó chỉ cần nhập tên obj cho mỗi lần lồng mà bạn muốn xử lý ...

<TextField
 name="street"
 onChange={handleChange('address')}
 />

4

Tôi đã sử dụng giải pháp này.

Nếu bạn có một trạng thái lồng nhau như thế này:

   this.state = {
          formInputs:{
            friendName:{
              value:'',
              isValid:false,
              errorMsg:''
            },
            friendEmail:{
              value:'',
              isValid:false,
              errorMsg:''
            }
}

bạn có thể khai báo hàm handleChange sao chép trạng thái hiện tại và gán lại nó với các giá trị đã thay đổi

handleChange(el) {
    let inputName = el.target.name;
    let inputValue = el.target.value;

    let statusCopy = Object.assign({}, this.state);
    statusCopy.formInputs[inputName].value = inputValue;

    this.setState(statusCopy);
  }

đây là html với trình nghe sự kiện

<input type="text" onChange={this.handleChange} " name="friendName" />

Đã xác nhận. Điều này hoạt động rất tốt nếu bạn đang xử lý một dự án đã được thiết lập với các thuộc tính lồng nhau.
metal_jacke1

4

Tạo một bản sao của nhà nước:

let someProperty = JSON.parse(JSON.stringify(this.state.someProperty))

thực hiện thay đổi trong đối tượng này:

someProperty.flag = "false"

bây giờ cập nhật trạng thái

this.setState({someProperty})

setState không đồng bộ. Vì vậy, trong khi cập nhật, người khác có thể gọi chức năng này và sao chép phiên bản lỗi thời của trạng thái.
Aviv Ben Shabat

3

Mặc dù bạn đã hỏi về trạng thái của thành phần React dựa trên lớp, nhưng vấn đề tương tự vẫn tồn tại với hook useState. Thậm chí tệ hơn: hook useState không chấp nhận cập nhật một phần. Vì vậy, câu hỏi này trở nên rất phù hợp khi hook useState được giới thiệu.

Tôi đã quyết định đăng câu trả lời sau để đảm bảo câu hỏi bao gồm các tình huống hiện đại hơn, nơi sử dụng hook useState:

Nếu bạn có:

const [state, setState] = useState({ someProperty: { flag: true, otherNestedProp: 1 }, otherProp: 2 })

bạn có thể đặt thuộc tính lồng nhau bằng cách nhân bản hiện tại và vá các phân đoạn dữ liệu cần thiết, ví dụ:

setState(current => { ...current, someProperty: { ...current.someProperty, flag: false } });

Hoặc bạn có thể sử dụng thư viện Immer để đơn giản hóa việc nhân bản và vá lỗi của đối tượng.

Hoặc bạn có thể sử dụng thư viện Hookstate (từ chối trách nhiệm: Tôi là tác giả) để đơn giản quản lý toàn bộ dữ liệu trạng thái (cục bộ và toàn cầu) và cải thiện hiệu suất (đọc: không phải lo lắng về tối ưu hóa kết xuất):

import { useStateLink } from '@hookstate/core' 
const state = useStateLink({ someProperty: { flag: true, otherNestedProp: 1 }, otherProp: 2 })

lấy trường để kết xuất:

state.nested.someProperty.nested.flag.get()
// or 
state.get().someProperty.flag

đặt trường lồng nhau:

state.nested.someProperty.nested.flag.set(false)

Dưới đây là ví dụ Hookstate, trong đó trạng thái được lồng sâu / đệ quy trong cấu trúc dữ liệu giống như cây .


2

Hai tùy chọn khác chưa được đề cập:

  1. Nếu bạn có trạng thái lồng nhau sâu sắc, hãy xem xét nếu bạn có thể cấu trúc lại các đối tượng con để ngồi ở gốc. Điều này làm cho dữ liệu dễ dàng cập nhật hơn.
  2. Có nhiều thư viện tiện dụng có sẵn để xử lý trạng thái bất biến được liệt kê trong tài liệu Redux . Tôi khuyên dùng Immer vì nó cho phép bạn viết mã theo cách đột biến nhưng xử lý việc nhân bản cần thiết đằng sau hậu trường. Nó cũng đóng băng đối tượng kết quả để bạn không thể vô tình biến đổi nó sau này.

2

Để làm cho mọi thứ chung chung, tôi đã làm việc trên các câu trả lời của @ ShubhamKhatri và @ Qwerty.

đối tượng nhà nước

this.state = {
  name: '',
  grandParent: {
    parent1: {
      child: ''
    },
    parent2: {
      child: ''
    }
  }
};

điều khiển đầu vào

<input
  value={this.state.name}
  onChange={this.updateState}
  type="text"
  name="name"
/>
<input
  value={this.state.grandParent.parent1.child}
  onChange={this.updateState}
  type="text"
  name="grandParent.parent1.child"
/>
<input
  value={this.state.grandParent.parent2.child}
  onChange={this.updateState}
  type="text"
  name="grandParent.parent2.child"
/>

Phương thức updateState

setState là câu trả lời của @ ShubhamKhatri

updateState(event) {
  const path = event.target.name.split('.');
  const depth = path.length;
  const oldstate = this.state;
  const newstate = { ...oldstate };
  let newStateLevel = newstate;
  let oldStateLevel = oldstate;

  for (let i = 0; i < depth; i += 1) {
    if (i === depth - 1) {
      newStateLevel[path[i]] = event.target.value;
    } else {
      newStateLevel[path[i]] = { ...oldStateLevel[path[i]] };
      oldStateLevel = oldStateLevel[path[i]];
      newStateLevel = newStateLevel[path[i]];
    }
  }
  this.setState(newstate);
}

setState là câu trả lời của @ Qwerty

updateState(event) {
  const path = event.target.name.split('.');
  const depth = path.length;
  const state = { ...this.state };
  let ref = state;
  for (let i = 0; i < depth; i += 1) {
    if (i === depth - 1) {
      ref[path[i]] = event.target.value;
    } else {
      ref = ref[path[i]];
    }
  }
  this.setState(state);
}

Lưu ý: Những phương pháp trên sẽ không hoạt động cho mảng


2

Tôi rất coi trọng những lo ngại đã được đưa ra xung quanh việc tạo ra một bản sao hoàn chỉnh của trạng thái thành phần của bạn. Với những gì đã nói, tôi sẽ đề nghị Immer .

import produce from 'immer';

<Input
  value={this.state.form.username}
  onChange={e => produce(this.state, s => { s.form.username = e.target.value }) } />

Điều này sẽ làm việc cho React.PureComponent(tức là so sánh trạng thái nông bằng React) khi Immersử dụng khéo léo một đối tượng proxy để sao chép hiệu quả một cây trạng thái sâu tùy ý. Immer cũng an toàn hơn so với các thư viện như Trình trợ giúp bất biến, và lý tưởng cho cả người dùng Javascript và Typecript.


Nguyên cảo chức năng tiện ích

function setStateDeep<S>(comp: React.Component<any, S, any>, fn: (s: 
Draft<Readonly<S>>) => any) {
  comp.setState(produce(comp.state, s => { fn(s); }))
}

onChange={e => setStateDeep(this, s => s.form.username = e.target.value)}

1
stateUpdate = () => {
    let obj = this.state;
    if(this.props.v12_data.values.email) {
      obj.obj_v12.Customer.EmailAddress = this.props.v12_data.values.email
    }
    this.setState(obj)
}

1
Tôi không nghĩ điều này là đúng, vì objchỉ là một tham chiếu đến tiểu bang, vì vậy thông qua nó, bạn đang biến đổi this.stateđối tượng thực tế
Ciprian Tomoiagă

0

Tôi thấy điều này hoạt động với tôi, có một mẫu dự án trong trường hợp của tôi, ví dụ như bạn có id, và tên và tôi muốn duy trì trạng thái cho một dự án lồng nhau.

return (
  <div>
      <h2>Project Details</h2>
      <form>
        <Input label="ID" group type="number" value={this.state.project.id} onChange={(event) => this.setState({ project: {...this.state.project, id: event.target.value}})} />
        <Input label="Name" group type="text" value={this.state.project.name} onChange={(event) => this.setState({ project: {...this.state.project, name: event.target.value}})} />
      </form> 
  </div>
)

Cho tôi biết!


0

Một cái gì đó như thế này có thể đủ,

const isObject = (thing) => {
    if(thing && 
        typeof thing === 'object' &&
        typeof thing !== null
        && !(Array.isArray(thing))
    ){
        return true;
    }
    return false;
}

/*
  Call with an array containing the path to the property you want to access
  And the current component/redux state.

  For example if we want to update `hello` within the following obj
  const obj = {
     somePrimitive:false,
     someNestedObj:{
        hello:1
     }
  }

  we would do :
  //clone the object
  const cloned = clone(['someNestedObj','hello'],obj)
  //Set the new value
  cloned.someNestedObj.hello = 5;

*/
const clone = (arr, state) => {
    let clonedObj = {...state}
    const originalObj = clonedObj;
    arr.forEach(property => {
        if(!(property in clonedObj)){
            throw new Error('State missing property')
        }

        if(isObject(clonedObj[property])){
            clonedObj[property] = {...originalObj[property]};
            clonedObj = clonedObj[property];
        }
    })
    return originalObj;
}

const nestedObj = {
    someProperty:true,
    someNestedObj:{
        someOtherProperty:true
    }
}

const clonedObj = clone(['someProperty'], nestedObj);
console.log(clonedObj === nestedObj) //returns false
console.log(clonedObj.someProperty === nestedObj.someProperty) //returns true
console.log(clonedObj.someNestedObj === nestedObj.someNestedObj) //returns true

console.log()
const clonedObj2 = clone(['someProperty','someNestedObj','someOtherProperty'], nestedObj);
console.log(clonedObj2 === nestedObj) // returns false
console.log(clonedObj2.someNestedObj === nestedObj.someNestedObj) //returns false
//returns true (doesn't attempt to clone because its primitive type)
console.log(clonedObj2.someNestedObj.someOtherProperty === nestedObj.someNestedObj.someOtherProperty) 

0

Tôi biết đó là một câu hỏi cũ nhưng vẫn muốn chia sẻ làm thế nào tôi đạt được điều này. Giả sử trạng thái trong constructor trông như thế này:

  constructor(props) {
    super(props);

    this.state = {
      loading: false,
      user: {
        email: ""
      },
      organization: {
        name: ""
      }
    };

    this.handleChange = this.handleChange.bind(this);
  }

handleChangeChức năng của tôi là như thế này:

  handleChange(e) {
    const names = e.target.name.split(".");
    const value = e.target.type === "checkbox" ? e.target.checked : e.target.value;
    this.setState((state) => {
      state[names[0]][names[1]] = value;
      return {[names[0]]: state[names[0]]};
    });
  }

Và chắc chắn rằng bạn đặt tên đầu vào phù hợp:

<input
   type="text"
   name="user.email"
   onChange={this.handleChange}
   value={this.state.user.firstName}
   placeholder="Email Address"
/>

<input
   type="text"
   name="organization.name"
   onChange={this.handleChange}
   value={this.state.organization.name}
   placeholder="Organization Name"
/>

0

Tôi thực hiện cập nhật lồng nhau với một tìm kiếm giảm :

Thí dụ:

Các biến lồng nhau trong trạng thái:

state = {
    coords: {
        x: 0,
        y: 0,
        z: 0
    }
}

Chức năng:

handleChange = nestedAttr => event => {
  const { target: { value } } = event;
  const attrs = nestedAttr.split('.');

  let stateVar = this.state[attrs[0]];
  if(attrs.length>1)
    attrs.reduce((a,b,index,arr)=>{
      if(index==arr.length-1)
        a[b] = value;
      else if(a[b]!=null)
        return a[b]
      else
        return a;
    },stateVar);
  else
    stateVar = value;

  this.setState({[attrs[0]]: stateVar})
}

Sử dụng:

<input
value={this.state.coords.x}
onChange={this.handleTextChange('coords.x')}
/>

0

Đây là chữ cái đầu tiên của tôi

    const initialStateInput = {
        cabeceraFamilia: {
            familia: '',
            direccion: '',
            telefonos: '',
            email: ''
        },
        motivoConsulta: '',
        fechaHora: '',
        corresponsables: [],
    }

Móc hoặc bạn có thể thay thế nó bằng trạng thái (thành phần lớp)

const [infoAgendamiento, setInfoAgendamiento] = useState(initialStateInput);

Phương thức xử lýChange

const actualizarState = e => {
    const nameObjects = e.target.name.split('.');
    const newState = setStateNested(infoAgendamiento, nameObjects, e.target.value);
    setInfoAgendamiento({...newState});
};

Phương pháp thiết lập trạng thái với trạng thái lồng nhau

const setStateNested = (state, nameObjects, value) => {
    let i = 0;
    let operativeState = state;
    if(nameObjects.length > 1){
        for (i = 0; i < nameObjects.length - 1; i++) {
            operativeState = operativeState[nameObjects[i]];
        }
    }
    operativeState[nameObjects[i]] = value;
    return state;
}

Cuối cùng, đây là đầu vào mà tôi sử dụng

<input type="text" className="form-control" name="cabeceraFamilia.direccion" placeholder="Dirección" defaultValue={infoAgendamiento.cabeceraFamilia.direccion} onChange={actualizarState} />

0

Nếu bạn đang sử dụng formik trong dự án của mình, nó có một số cách dễ dàng để xử lý công cụ này. Đây là cách dễ nhất để làm với formik.

Đầu tiên, đặt các giá trị ban đầu của bạn bên trong thuộc tính formik initivalues ​​hoặc trong phản ứng. tiểu bang

Ở đây, các giá trị ban đầu được xác định ở trạng thái phản ứng

   state = { 
     data: {
        fy: {
            active: "N"
        }
     }
   }

xác định ở trên initValues ​​cho trường formik bên trong initiValuesthuộc tính formik

<Formik
 initialValues={this.state.data}
 onSubmit={(values, actions)=> {...your actions goes here}}
>
{({ isSubmitting }) => (
  <Form>
    <Field type="checkbox" name="fy.active" onChange={(e) => {
      const value = e.target.checked;
      if(value) setFieldValue('fy.active', 'Y')
      else setFieldValue('fy.active', 'N')
    }}/>
  </Form>
)}
</Formik>

Tạo một bàn điều khiển để kiểm tra trạng thái được cập nhật stringthay vì hàm booleanformik setFieldValueđể đặt trạng thái hoặc đi với công cụ gỡ lỗi phản ứng để xem các thay đổi bên trong các giá trị trạng thái formik.


0

thử mã này:

this.setState({ someProperty: {flag: false} });

Nhưng điều này sẽ xóa bất kỳ thuộc tính nào khác có trong đối tượng được tham chiếu bởi somePropertyvà sau khi đoạn mã trên được thực thi, chỉ có thuộc flagtính duy trì
Yashas

0

tôi đã thấy sau đây trong một cuốn sách:

this.setState(state => state.someProperty.falg = false);

nhưng tôi không chắc liệu nó có đúng không ..

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.