React Native:TextInput with state和AsyncStorage

时间:2018-05-08 12:47:54

标签: javascript android ios reactjs react-native

在键盘上输入时,我看到一些关于输入在JS代码之前的警告..

  

Native TextInput(本机反应很棒)是JS之前的4个事件 - 尝试让你的JS更快。

所以添加了debounce并将其设为“工作”:

...
import { debounce } from 'lodash'
...
export default class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      data,
      indexRef: data.reduce((result, item, index) => {
        result[item.title] = index
        return result
      }, {}),
      ready: false,
    }

    this.updatePitch = this.updatePitch.bind(this)
    this.saveLocally = debounce(this.saveLocally, 300).bind(this)
  }
  ...
  updatePitch(id, text) {
    // Copy the data
    let data = [...this.state.data]
    const index = data.findIndex(obj => obj.id == id)
    data[index].pitch = text
    // Update the state
    this.setState({ data }, this.saveLocally(data))
  }

  saveLocally(data) {
    try {
      AsyncStorage.setItem('data', JSON.stringify(data))
      this.forceUpdate()
    } catch (error) {
      // Well..
    }
  }

  render() {
  ...
顺便说一句:我现在正在做一些“道具钻探” - 但计划使用Context API (react 16.3)

通过添加debounce(1),警告似乎已经消失了......但是我看到了一些奇怪的问题 - 特别是在iPhone 8 plus模拟器上(在iPhone 6模拟器或Android上看不到相同的问题)装置)

观察到的问题:

  • TextInput不展开 - 它只是添加了scolling(在iPhone 6和Android设备上展开)
  • FlatList中的一些布局问题 - 似乎在找到列表元素的正确高度时遇到了问题。

快速JS代码和保存到stateAsyncStorage的最佳做法是什么?

(1)除了使用debounce之外的另一种方法可能是使用getDerivedStateFromProps并添加某种计时器,在一段时间后将状态推送到父组件。但是不确定这将使JS代码更快。所以没试过。

更新(再次)

Open Source

我开源了整个代码,因为当代码嵌套时很难在SO帖子中提供所有需要的信息。

整个代码在这里: https://github.com/Norfeldt/LionFood_FrontEnd

(我知道我的代码可能看起来很乱,但我还在学习......)

我不希望人们进入并使用PR修复我的代码(即使它很棒),但只是给我一些关于如何正确处理stateAsyncStorage的代码指导TextInput

我知道我有一些风格问题 - 很想修复它们,但也要遵守SO并保持主题。

更新II

我删除了forceUpdate,并将FadeImage替换为只有香草的原生Image

但我仍然看到一些奇怪的问题

Weird behavior

这是我的代码

import React from 'react'
import {
  StyleSheet,
  SafeAreaView,
  FlatList,
  StatusBar,
  ImageBackground,
  AsyncStorage,
  Platform,
} from 'react-native'
import SplashScreen from 'react-native-splash-screen'
import LinearGradient from 'react-native-linear-gradient'
import { debounce } from 'lodash'

import Section from './Section'
import ButtonContact from './ButtonContact'

import { data } from '../data.json'

export default class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      data,
      indexRef: data.reduce((result, item, index) => {
        result[item.title] = index
        return result
      }, {}),
      ready: false,
    }
  }

  async componentDidMount() {
    SplashScreen.hide()
    try {
      let BusinessPlan = await AsyncStorage.getItem('BusinessPlan')
      if (BusinessPlan !== null) {
        // We have data!!
        let data = JSON.parse(BusinessPlan)
        data = this.state.data.map(item => {
          const index = data.findIndex(obj => obj.id == item.id)
          const pitch = index >= 0 ? data[index].pitch : ''
          return { ...item, pitch }
        })
        this.setState({ data, ready: true })
      } else {
        this.setState({ ready: true })
      }
    } catch (error) {
      // Error retrieving data
    }
  }

  updatePitch = (id, text) => {
    // Copy the data
    let data = [...this.state.data]
    const index = data.findIndex(obj => obj.id == id)
    data[index].pitch = text
    // Update the state
    this.setState({ data }, this.saveLocally(data))
  }

  saveLocally = data => {
    try {
      AsyncStorage.setItem('BusinessPlan', JSON.stringify(data))
    } catch (error) {
      // Well..
    }
  }

  render() {
    return (
      <LinearGradient
        style={{ flex: 1 }}
        start={{ x: 0.0, y: 0.25 }}
        end={{ x: 0.5, y: 1.0 }}
        colors={['#000000', '#808080', '#000000']}
      >
        <StatusBar
          barStyle={'light-content'}
          backgroundColor={Platform.OS == 'iOS' ? 'transparent' : 'black'}
        />
        <SafeAreaView>
          <ImageBackground
            source={require('../images/BackgroundImage.png')}
            style={{ width: '100%', height: '100%' }}
            resizeMode={'cover'}
          >
            <FlatList
              data={this.state.data}
              initialNumToRender="16"
              keyExtractor={item => item.id}
              renderItem={({ item }) => (
                <Section
                  id={item.id}
                  title={item.title}
                  pitch={item.pitch}
                  updatePitch={debounce(this.updatePitch, 1000)}
                  questions={item.questions}
                  ready={this.state.ready}
                />
              )}
              ListFooterComponent={<ButtonContact />}
              style={{
                backgroundColor: 'transparent',
                borderColor: '#000',
                borderWidth: StyleSheet.hairlineWidth,
              }}
            />
          </ImageBackground>
        </SafeAreaView>
      </LinearGradient>
    )
  }
}

const styles = StyleSheet.create({
  sectionHeader: {
    fontSize: 24,
    marginHorizontal: 5,
  },
})

(我还更新了我的git repo)

更新III

似乎我对stateAsyncStorage的设置适用于debounce。我看到的问题是因为我正在耗尽CPU(下一步要修复)。

1 个答案:

答案 0 :(得分:3)

我尝试了你的代码:

  • &#34;我看到了一些奇怪的问题 - 特别是在iPhone 8 plus上 模拟器(在iPhone 6模拟器或Android上看不到相同 装置)&#34; ==&GT;我确认了这个

  • 该应用程序需要大约100%的CPU。

经过一段时间的尝试,我发现了:

  • &#34;我看到了一些奇怪的问题 - 特别是在iPhone 8 plus上 模拟器(在iPhone 6模拟器或Android上看不到相同 装置)&#34; ==&GT;不对,只需等待一点TextInput就会扩大。

  • state和AsyncStorage没有任何问题。我没有得到任何警告。

根问题是FadeImage中的动画:

  • 该应用会呈现多个Carousel,每个Carousel都有AngleInvestorFadeImage。问题是FadeImage

  • FadeImage运行Animated,持续时间为1000 =&gt; CPU过载

    ==&GT;为什么TextInput添加滚动然后需要很长时间才能展开,FlatList看起来有问题,但不是。它们只是慢慢更新。

解决方案:

  • 尝试发表评论FadeImage,您会看到问题消失。

  • 不要同时开始这么多动画。如果出现就开始(例如:Carousel中的第一张卡片)

<强>更新

我遇到了您的问题:快速输入会导致setState多次调用。 您可以使用debounce来解决这种情况:

在App.js中

render() {
    console.log('render app.js')
    ...
                <Section
                  id={item.id}
                  title={item.title}
                  pitch={item.pitch}
                  updatePitch={debounce(this.updatePitch, 1000)} // the key is here
                  questions={item.questions}
                  ready={this.state.ready}
                />

您可以更改延迟并观看控制台日志以查看更多信息。我试过,延迟大约500可以停止警告。

P / s:您应该尝试删除forceUpdate