2017-07-25 25 views
6

Vấn đề đơn giản. Gọi this.setState bên trong Promise, hiển thị trước khi kết thúc đang chờ Promise.this.setState bên trong Lời hứa gây ra hành vi lạ

vấn đề của tôi là:

  1. Các this.setState không phải là lập tức trở
    • tôi mong đợi nó được async, do đó lời hứa chưa xử lý sẽ bị đóng cửa đầu tiên.
  2. Nếu một thứ gì đó sẽ phá vỡ bên trong hàm hiển thị, bắt trong Lời hứa được gọi.
    • Có thể vấn đề tương tự như 1) có vẻ như kết xuất vẫn nằm trong ngữ cảnh lời hứa trong đó this.setState được gọi.

import dummydata_rankrequests from "../dummydata/rankrequests"; 
class RankRequestList extends Component { 

    constructor(props) { 
    super(props); 

    this.state = { loading: false, data: [], error: null }; 

    this.makeRankRequestCall = this.makeRankRequestCall.bind(this); 
    this.renderItem = this.renderItem.bind(this); 
    } 

    componentDidMount() { 

    // WORKS AS EXPECTED 
    // console.log('START set'); 
    // this.setState({ data: dummydata_rankrequests.data, loading: false }); 
    // console.log('END set'); 

    this.makeRankRequestCall() 
    .then(done => { 
     // NEVER HERE 
     console.log("done"); 
    });  
    } 

    makeRankRequestCall() { 
    console.log('call makeRankRequestCall'); 
    try { 
     return new Promise((resolve, reject) => { 
     resolve(dummydata_rankrequests); 
     }) 
     .then(rankrequests => { 
     console.log('START makeRankRequestCall-rankrequests', rankrequests); 
     this.setState({ data: rankrequests.data, loading: false }); 
     console.log('END _makeRankRequestCall-rankrequests'); 
     return null; 
     }) 
     .catch(error => { 
     console.log('_makeRankRequestCall-promisecatch', error); 
     this.setState({ error: RRError.getRRError(error), loading: false }); 
     }); 
    } catch (error) { 
     console.log('_makeRankRequestCall-catch', error); 
     this.setState({ error: RRError.getRRError(error), loading: false }); 
    } 
    } 

    renderItem(data) { 
    const height = 200; 
    // Force a Unknown named module error here 
    return (
     <View style={[styles.item, {height: height}]}> 
     </View> 
    ); 
    } 

    render() { 
    let data = []; 
    if (this.state.data && this.state.data.length > 0) { 
     data = this.state.data.map(rr => { 
     return Object.assign({}, rr); 
     }); 
    } 
    console.log('render-data', data); 
    return (
     <View style={styles.container}> 
     <FlatList style={styles.listContainer1} 
      data={data} 
      renderItem={this.renderItem} 
     /> 
     </View> 
    ); 
    } 
} 

bản ghi Currrent cho thấy:

  • render-dữ liệu, []
  • BẮT ĐẦU makeRankRequestCall-rankrequests
  • render-dữ liệu, [. ..]
  • _makeRankRequestCall-promisecatch Lỗi: Không rõ tên mô-đun ...
  • render-dữ liệu, [...]
  • thể Unhandled Promise

Android Emulator "phản ứng": "16.0.0- alpha.12" , "phản ứng bản địa": "0.46.4",

EDIT: gói setTimeout xung quanh this.setState cũng làm việc

setTimeout(() => { 
     this.setState({ data: respData.data, loading: false }); 
    }, 1000); 

EDIT2: tạo ra một báo cáo lỗi trong github phản ứng bản địa song song https://github.com/facebook/react-native/issues/15214

+0

Tôi đang gặp rắc rối xác định chính xác những gì vấn đề mà bạn đang cố gắng để giải quyết. Bạn đang cố gắng tái render chỉ một lần tại thời điểm 'console.log (" done ");' được thực hiện? Nếu vậy, một cách để đạt được nó là ghi đè 'shouldComponentUpdate()' để nó luôn trả về 'false' và' this.forceUpdate' khi bạn sẵn sàng tái kết xuất. https://facebook.github.io/react/docs/react-component.html#forceupdate – therobinkim

Trả lời

1

Cả Promisethis.setState() là không đồng bộ trong javascript. Giả sử, nếu bạn có mã sau:

console.log(a); 
networkRequest().then(result => console.log(result)); // networkRequest() is a promise 
console.log(b); 

Dấu a và b sẽ được in trước sau kết quả của yêu cầu mạng.

Tương tự, this.setState() cũng là không đồng bộ như vậy, nếu bạn muốn thực hiện một cái gì đó sau khi this.setState() hoàn tất, bạn cần phải làm điều đó như:

this.setState({data: rankrequests.data},() => { 
    // Your code that needs to run after changing state 
}) 

Phản ứng lại làm cho mỗi lần this.setState() được thực hiện, do đó bạn việc cập nhật thành phần của bạn trước khi toàn bộ lời hứa được giải quyết.Vấn đề này có thể được giải quyết bằng cách làm cho componentDidMount() của bạn như chức năng async và sử dụng đang chờ đợi để giải quyết lời hứa:

async componentDidMount() { 
    let rankrequests; 
    try { 
    rankrequests = await this.makeRankRequestCall() // result contains your data 
    } catch(error) { 
    console.error(error); 
    } 
    this.setState({ data: rankrequests.data, loading: false },() => { 
    // anything you need to run after setting state 
    }); 
} 

Hy vọng nó giúp.

+0

Tôi sẽ không khuyên bạn thay đổi thứ gì đó quan trọng như các phương pháp RN-Vòng đời của một thành phần nhất định. Làm cho nó ASync có thể kết thúc là một sai lầm lớn. – GoreDefex

+0

Ngoài ra, nếu bạn đang làm bất cứ điều gì để đáp ứng với một cập nhật trạng thái, bạn nên sử dụng phương pháp RN-Lifecycle 'componentDidUpdate' – GoreDefex

1

Tôi cũng đang gặp khó khăn khi hiểu những gì bạn đang cố gắng làm ở đây vì vậy tôi đã đâm vào nó.

Vì phương pháp this.setState() nhằm kích hoạt hiển thị, tôi sẽ không bao giờ gọi nó cho đến khi bạn sẵn sàng kết xuất. Bạn dường như dựa nhiều vào biến trạng thái được cập nhật và có thể được sử dụng/thao tác theo ý muốn. Hành vi mong đợi ở đây, của biến số this.state., sẽ sẵn sàng tại thời điểm hiển thị. Tôi nghĩ bạn cần phải sử dụng một biến khác có thể biến đổi hơn mà không bị ràng buộc với các trạng thái và hiển thị. Khi bạn hoàn thành, và chỉ sau đó, bạn sẽ được dựng hình.

Đây là mã của bạn lại làm việc để hiển thị này sẽ xem xét:

dummydata_rankrequests nhập khẩu từ "../dummydata/rankrequests";

lớp RankRequestList kéo dài Component {

constructor(props) { 
    super(props); 

    /* 
     Maybe here is a good place to model incoming data the first time? 
     Then you can use that data format throughout and remove the heavier modelling 
     in the render function below 

     if (this.state.data && this.state.data.length > 0) { 
      data = this.state.data.map(rr => { 
       return Object.assign({}, rr); 
      }); 
     } 
    */ 

    this.state = { 
     error: null, 
     loading: false, 
     data: (dummydata_rankrequests || []), 
    }; 

    //binding to 'this' context here is unnecessary 
    //this.makeRankRequestCall = this.makeRankRequestCall.bind(this); 
    //this.renderItem = this.renderItem.bind(this); 
} 


componentDidMount() { 
    // this.setState({ data: dummydata_rankrequests.data, loading: false }); 

    //Context of 'this' is already present in this lifecycle component 
    this.makeRankRequestCall(this.state.data).then(returnedData => { 
     //This would have no reason to be HERE before, you were not returning anything to get here 
     //Also, 
     //should try not to use double quotes "" in Javascript 


     //Now it doesn't matter WHEN we call the render because all functionality had been returned and waited for 
     this.setState({ data: returnedData, loading: false }); 

    }).catch(error => { 
     console.log('_makeRankRequestCall-promisecatch', error); 
     this.setState({ error: RRError.getRRError(error), loading: false }); 
    }); 
} 


//I am unsure why you need a bigger call here because the import statement reads a JSON obj in without ASync wait time 
//...but just incase you need it... 
async makeRankRequestCall(currentData) { 
    try { 
     return new Promise((resolve, reject) => { 
      resolve(dummydata_rankrequests); 

     }).then(rankrequests => { 
      return Promise.resolve(rankrequests); 

     }).catch(error => { 
      return Promise.reject(error); 
     }); 

    } catch (error) { 
     return Promise.reject(error); 
    } 
} 


renderItem(data) { 
    const height = 200; 

    //This is usually where you would want to use your data set 
    return (
     <View style={[styles.item, {height: height}]} /> 
    ); 

    /* 
     //Like this 
     return { 
      <View style={[styles.item, {height: height}]}> 
       { data.item.somedataTitleOrSomething } 
      </View> 
     }; 
    */ 
} 


render() { 
    let data = []; 

    //This modelling of data on every render will cause a huge amount of heaviness and is not scalable 
    //Ideally things are already modelled here and you are just using this.state.data 
    if (this.state.data && this.state.data.length > 0) { 
     data = this.state.data.map(rr => { 
      return Object.assign({}, rr); 
     }); 
    } 
    console.log('render-data', data); 

    return (
     <View style={styles.container}> 
      <FlatList 
       data={data} 
       style={styles.listContainer1} 
       renderItem={this.renderItem.bind(this)} /> 
      { /* Much more appropriate place to bind 'this' context than above */ } 
     </View> 
    ); 
} 

}

1

Các setState thực sự là không đồng bộ. Tôi đoán makeRankRequestCall nên như thế này:

async makeRankRequestCall() { 
    console.log('call makeRankRequestCall'); 
    try { 
    const rankrequests = await new Promise((resolve, reject) => { 
     resolve(dummydata_rankrequests); 
    }); 

    console.log('START makeRankRequestCall-rankrequests', rankrequests); 
    this.setState({ data: rankrequests.data, loading: false }); 
    console.log('END _makeRankRequestCall-rankrequests'); 
    } catch(error) { 
    console.log('_makeRankRequestCall-catch', error); 
    this.setState({ error: RRError.getRRError(error), loading: false }); 
    } 
} 

Thứ hai, lời hứa bắt lỗi của renderItem là hoàn toàn tốt đẹp. Trong JavaScript, bất kỳ khối catch nào sẽ bắt gặp bất kỳ lỗi nào đang được ném ra bất kỳ đâu trong mã. Theo specs:

The throw statement throws a user-defined exception. Execution of the current function will stop (the statements after throw won't be executed), and control will be passed to the first catch block in the call stack. If no catch block exists among caller functions, the program will terminate.

Vì vậy, để khắc phục nó, nếu bạn mong đợi renderItem thất bại, bạn có thể làm như sau:

renderItem(data) { 
    const height = 200; 
    let item = 'some_default_item'; 
    try { 
    // Force a Unknown named module error here 
    item = styles.item 
    } catch(err) { 
    console.log(err); 
    } 
    return (
    <View style={[item, {height: height}]}> 
    </View> 
); 
}