【问题标题】:Building a ListView with both API and AsyncStorage Data使用 API 和 AsyncStorage 数据构建 ListView
【发布时间】:2015-10-29 20:53:28
【问题描述】:

要构建我的 React Native ListView,我需要从两个地方提取数据,从网络 APi 和从 AsyncStorage(如 AppCache)。来自 AsyncStorage 的数据可能存在也可能不存在,但它需要以任何一种方式返回(例如“未找到”)

这是当前版本的要点,除了检索 cachedOn 日期 (Line 47) https://gist.github.com/geirman/1901d4b1bfad42ec6d65#file-aircraftlist-js-L47,我相信这就是秘诀所在。

我认为这可能是任何 ReactJS 开发人员都可能回答的问题,尽管该示例是特定于 React Native 的。

【问题讨论】:

  • 你有没有想过解决这个问题?我正在尝试解决同样的问题。
  • 是的,但我对这个解决方案从来都不满意。您可以在 github github.com/geirman/RepairMaps 上查看我的代码,不过感谢您再次关注这个问题。看起来有几个新的答案要审核!

标签: listview reactjs react-native


【解决方案1】:

这个问题看起来相当复杂,因为有多个级别的异步在起作用:获取数据、读/写缓存和渲染列表行。在这种情况下,将问题分解为更小的组件通常会有所帮助。

我无法轻松运行示例代码,因此我使用了一个简化的示例。

首先,让我们将缓存包装成一个简洁的接口,这样我们在使用它时就不需要考虑AsyncStorage 语义:

const aircraftCache = {
  // returns promise of cached aircraft, or null if not found
  getAircraft(aircraftId) {
    return AsyncStorage.getItem(aircraftId).then(data => (
      data ? JSON.parse(data) : null
    ));
  },

  // caches given aircraft object with a fresh cachedOn date
  // and returns a promise of the cached aircraft
  setAircraft(aircraftId, aircraft) {
    const cached = {...aircraft, cachedOn: new Date()};
    return AsyncStorage.setItem(aircraftId, JSON.stringify(cached)).then(() => cached);
  },

  // clears given aircraft from cache and return Promise<null>
  clearAircraft(aircraftId) {
    return AsyncStorage.removeItem(aircraftId).then(() => null);
  }
}

然后,让我们将AircraftList 的职责限制为仅显示数据列表、加载指示器等,并将行渲染提取到单独的组件中:

class AircraftList extends Component {
  static propTypes = {
    aircraft_list: PropTypes.arrayOf(PropTypes.shape({
      reg_number: PropTypes.string,
      ti_count: PropTypes.number
    }))
  }

  constructor(props) {
    super(props);
    this.ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
    this.state = {
      dataSource: this.ds.cloneWithRows(this.props.aircraft_list),
      isLoading: false,
      showingCache: false
    };
  }

  aircraftLoaded(aircraft) {
    this.setState({isLoading: false});
    this.props.navigator.push({
      title: 'TI Lookup',
      component: TrackedItemIndex,
      passProps: {aircraft_object: aircraft}
    });
  }

  renderRow(aircraft) {
    return (
      <AircraftRow
        reg_number={aircraft.reg_number}
        ti_count={aircraft.ti_count}
        loading={() => this.setState({isLoading: true})}
        loaded={this.aircraftLoaded.bind(this)}
      />
    );
  }

  render() {
    // simplified view
    return(
      <ListView
        dataSource={this.state.dataSource}
        renderRow={this.renderRow.bind(this)}
      />
    );
  }
}

然后可以将单独的行渲染、获取和缓存操作封装到AircraftRow 组件中:

class AircraftRow extends Component {
  static propTypes = {
    reg_number: PropTypes.string,
    ti_count: PropTypes.number,
    loading: PropTypes.func,
    loaded: PropTypes.func
  }

  state = { cachedOn: null };

  constructor(props) {
    super(props);

    this.loadDetails = this.loadDetails.bind(this);
    this.clearDetails = this.clearDetails.bind(this);
    this.setCachedOn = this.setCachedOn.bind(this);
  }

  componentWillMount() {
    // when component is loaded, look up the cached details and
    // set the cachedOn timestamp into state
    aircraftCache.getAircraft(this.props.reg_number).then(this.setCachedOn);
  }

  loadDetails() {
    const id = this.props.reg_number;
    // notify parent that loading has started
    if (this.props.loading) {
      this.props.loading(id);
    }

    // fetch and cache the data
    this.fetchDetails(id)
      .then((aircraft) => {
        // notify parent that loading has finished
        if (this.props.loaded) {
          this.props.loaded(aircraft);
        }
      })
      .catch((e) => {
        console.error(e);
      });
  }

  fetchDetails(id) {
    // get details from the api, and fall back to the cached copy
    return Api.getTrackedItems(id)
      .then(aircraft => aircraftCache.setAircraft(id, aircraft))
      .then(this.setCachedOn)
      .catch(() => aircraftCache.getAircraft(id));
  }

  clearDetails() {
    // clear item from cache and update local state with null aircraft
    const id = this.props.reg_number;
    aircraftCache.clearAircraft(id).then(this.setCachedOn);
  }

  setCachedOn(aircraft) {
    // update local state (aircraft can be null)
    this.setState({ cachedOn: aircraft ? aircraft.cachedOn.toString() : null })
    return aircraft;
  }

  render() {
    // simplified view
    return (
      <View>
        <Text>{this.props.reg_number}</Text>
        <Text>{this.props.ti_count}</Text>
        <Text>{this.state.cachedOn}</Text>
        <Text onPress={this.loadDetails}>Load details</Text>
        <Text onPress={this.clearDetails}>Clear details</Text>
      </View>
    )
  }
}

在我看来,这种观点仍然太过分了。我建议您研究诸如 Redux 或 MobX 之类的状态管理库来进一步简化代码——当然,它们也有自己的一套复杂性。

【讨论】:

    【解决方案2】:

    执行此操作的简单方法是通过id mapping

    我可以看到您的回复为每个项目提供了唯一的id。因此,将基于相同ids 的时间戳存储在本地存储中。当您从 api 映射结果的项目时,获取项目的 id 并将其传递给本地存储的 getItem()。这将为您返回 id

    的时间
    const randomTime = [{
      id: 1,
      date: '05-Jun-2032 14:37:11'
    }, {
      id: 2,
      date: '30-Jun-2006 00:02:27'
    }, {
      id: 4,
      date: '22-Aug-1996 02:47:28'
    }, {
      id: 6,
      date: '04-Jan-1991 23:27:15'
    }]
    
    const preProcessLocalStorage = () => {
      const data = JSON.parse(localStorage.getItem('date')) //read data from local storage
      const obj = {}
      data.forEach((el) => {
        obj[el.id] = el //convert into ids as keys object for better data retrieval 
      })
      return obj
    }
    class App extends React.Component{
      constructor(props){
        super(props)
        this.state = {
          loading: true,
        }
        this.apiData = []
        this.localData = []
        localStorage.setItem('date', JSON.stringify(randomTime)) //set Local data
      }
    
      componentDidMount() {
        this.localData = preProcessLocalStorage()
        $.get('https://jsonplaceholder.typicode.com/posts')
        .done((data) => {
          this.apiData = data
          this.setState({loading: false})
        })
      }
    
      render(){
        if(this.state.loading) return false
    
        const list = this.apiData.map((el) => {
          const time = this.localData[el.id] //find local data based on the api data id
          return <div>
            <h1>{el.id} - {el.title}</h1>
            <h4>{time || '-'}</h4>
          </div>
        })
    
        return <div>{list}</div>
      }
    }
    
    ReactDOM.render(<App/>, document.getElementById('app'))
    

    【讨论】:

      猜你喜欢
      • 2018-06-11
      • 2020-10-30
      • 2021-04-15
      • 2019-02-14
      • 2021-02-02
      • 2021-08-14
      • 1970-01-01
      • 1970-01-01
      • 2021-11-22
      相关资源
      最近更新 更多