[React Native] MobX + FlatList кто использует такую связку?
Пишем на React Native мобильное приложение... Состояние (массив объектов) хранится в MobX...
Решили использовать для отображения массива элементов компонент FlatList... Он все отображает но получаем в консоли "предупреждение" [mobx] Out of bounds read: 25 Загуглили что такое значит... :) Де попытка выводить элементы без отслеживания. Делали клон этого массива, хотели потом вывести клон. Дает ошибку, де включен строгий режим и вы пытаетесь менять данные из MobX... Есть у кого решение такой проблемы? Может кто имел с этим дело? Как поборол? |
Можешь накидать простенький пример, где воспроизводится?
"попытка выводить элементы без отслеживания" - не забыл навесить observer на компонент? |
Цитата:
Цитата:
|
Ну дак. Перед тем как передавать массив в FlatList или куда ещё, что тобой не контролируется - делай предварительно arr.slice()/toJS(arr).
Потому что массив в mobx - НЕ массив(arr instanceof Array // false), а массивоподобный mobx-объект. Почему они не взяли за основу обычный массив, как в Vue? А чтобы все страдали. |
Цитата:
Пишет, де включен строгий режим, вы пытаетесь менять отслеживаемые данные. Цитата:
|
Завтра покажу все наши "ипостаси" - посмотрите, может чего не так делали...
И тексты ошибок приведу полностью. |
Цитата:
|
Цитата:
|
Alexandroppolus, ох как же это больно. Больнее мне только от сраных рефов в Vue 3.
|
Цитата:
|
Цитата:
В консоли все нормально, но тут же ошибка про попытку менять отслеживаемые данные... Скоро поеду на работу - сделаем примеры, покажу тут. |
Ситуация получается более запутанной...
Сделал я тестик простенький и у меня все заработало без всяких предупреждений! Хранилище
import {makeAutoObservable} from 'mobx'
class Test {
data = {
arr: [
{id: '0', name: 'Item 0'},
{id: '1', name: 'Item 1'},
{id: '2', name: 'Item 2'},
]
}
constructor(props) {
makeAutoObservable(this)
}
add(obj) {
this.data.arr.push(obj)
}
}
export default new Test()
Собственно экран
import React from "react"
import { StyleSheet, Text, View, Button, FlatList } from 'react-native'
import { observer} from 'mobx-react-lite'
import { toJS } from "mobx"
import test from '../store/test'
function Test() {
//console.log(1, test.data.arr)
const arr = toJS(test.data.arr)
//console.log(2, test.data.arr)
const renderItem = ({ item }) => (
<Text>{item.name}</Text>
)
return (
<View style={styles.container}>
<View style={styles.container}>
<Button
title='Add'
onPress={add}
/>
</View>
<FlatList
data={test.data.arr}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</View>
)
function add() {
const l = test.data.arr.length
test.add({
id: l.toString(),
name: 'Item ' + l
})
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
tab: {
margin: 50
}
})
export default observer(Test)
Все рисуется и так нормально... НО! :) если убрать const arr = toJS(test.data.arr) Перестает работать добавление элементов. :blink: Возвращаю эту строку - работает добавление. А по предыдущей проблеме - сказал чтобы ребята начали искать у себя проблемы глубже... |
Цитата:
Было асинхронное чтение данных с сервера. Изменение состояния обернули в runInAction() из mobx и ошибка ушла. FlatList стал отображать данные из хранилища mobx без замечаний. Но по моему примеру все еще интересно что так влияет на работоспособность кнопки Add в моем примере? :-? |
Цитата:
без toJS ты читаешь только test.data.arr (и как следствие подписываешься только на изменение test.data.arr), а он не меняется, массив тот же самый, только в него запушили. вызов toJS(test.data.arr) делает чтение вообще всего, что есть внутри массива, и соответственно подписки будут на все изменения. и конечно, здесь не самая экономичная схема перерендеров. Что-то поменяли, и перерендеривается всё. Тут надо тоже смотреть по ситуации, как лучше сделать, и разумеется, без toJS |
Цитата:
|
Цитата:
Цитата:
Цитата:
В "простом" Реакте вполне хватало использования "сторных" методов и все перерисовывалось... А теперь этого мало... :( |
А потому, что он цепляется через жёпу. Потому что реакт - не расширяем нихрена. Вызовы рендера: стейт - отдельно, пропс - отдельно, нужен стор? - тоже отдельно.
Можешь попробовать энтот твой FlatList в <Observer><FlatList ...></Observer> заключить, но это так - выстрел в небо, загружать сейчас в голову весь контекст как оно там на самом деле работает откровенно лень.) |
Цитата:
Цитата:
я бы попробовал так: const MobxFlatList = observer(FlatList) и потом юзать MobxFlatList. А уж если такое не поможет - менять массив иммутабельно, то есть вместо test.data.arr.push(newItem) делать test.data.arr = [...test.data.arr, newItem] ну и там далее ещё кое-где поменять. использовать в рендере toJS - это антиMobX |
Цитата:
Не помогает. Цитата:
Цитата:
Цитата:
|
Цитата:
import React from "react"
import { StyleSheet, Text, View, Button, FlatList } from 'react-native'
import { observer} from 'mobx-react-lite'
import { toJS, runInAction } from "mobx"
import test from '../store/test'
function Test() {
//const arr = toJS(test.data.arr)
const renderItem = ({ item }) => (
<Text>{item.name}</Text>
)
const MobxFlatList = observer(FlatList)
return (
<View style={styles.container}>
<View style={styles.tab}>
<Button
title='Add'
onPress={add}
/>
</View>
<MobxFlatList
data={test.data.arr}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</View>
)
function add() {
const l = test.data.arr.length
test.add({
id: l.toString(),
name: 'Item ' + l
})
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
color: '#000',
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
tab: {
margin: 70
}
})
export default observer(Test)
Ошибка Cannot call a class as a function |
Цитата:
|
Цитата:
import {makeAutoObservable} from 'mobx'
class Test {
data = {
arr: [
{id: '0', name: 'Item 0'},
{id: '1', name: 'Item 1'},
{id: '2', name: 'Item 2'},
]
}
constructor(props) {
makeAutoObservable(this)
}
add(obj) {
//this.data.arr.push(obj)
this.data.arr = [...this.data.arr, obj]
}
}
export default new Test()
С этим работает и так. :victory:
import React from "react"
import { StyleSheet, Text, View, Button, FlatList } from 'react-native'
import { observer} from 'mobx-react-lite'
import { toJS, runInAction } from "mobx"
import test from '../store/test'
function Test() {
const renderItem = ({ item }) => (
<Text>{item.name}</Text>
)
return (
<View style={styles.container}>
<View style={styles.tab}>
<Button
title='Add'
onPress={add}
/>
</View>
<FlatList
data={test.data.arr}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</View>
)
function add() {
const l = test.data.arr.length
test.add({
id: l.toString(),
name: 'Item ' + l
})
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
color: '#000',
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
tab: {
margin: 70
}
})
export default observer(Test)
|
Цитата:
по кнопке случается все 3 вида изменений - две замены стейта, значение в пропсе, наблюдаемое значение в сторе. Перерендер один - он, похоже, идет следующим микротаском, потому что если store.func() обернуть в микротаск, то уже будет 2 рендера. в общем, реакт с мобиксом не так уж плохи :)
import { makeObservable, observable, action } from 'mobx'
import React, { useState } from 'react'
import { observer } from 'mobx-react-lite'
class Store {
constructor() {
makeObservable(this)
}
@observable a = 1
@observable b = 2
@action func() {
this.a += 1
this.b += 1
}
}
const store = new Store()
const Comp: React.FC<{ a: number }> = observer(({ a }) => {
const [c, setC] = useState(3)
const [d, setD] = useState(4)
console.log('*** render Comp ', a, store.b, c, d)
const handler = () => {
setC(c + 1)
setD(d + 1)
// Promise.resolve().then(() => store.func())
store.func()
}
return (
<div>
<div>{`a = ${a}, store.b = ${store.b}, c = ${c}, d = ${d}`}</div>
<button type="button" onClick={handler}>
change
</button>
</div>
)
})
export const App: React.FC = observer(() => {
console.log('*** render App')
return <Comp a={store.a} />
})
|
Цитата:
import { observer} from 'mobx-react' ещё поправки, для чистоты эксперимента: const MobxFlatList = observer(FlatList) это вынеси наружу из function Test() const renderItem = ... тоже вынеси функцию для keyExtractor тоже вынеси отдельно наружу |
Alexandroppolus, я все это "запишу себе в книжечка"... (с)
Мало ли когда пригодится. :) Больше устроил вариант с другим подходом собственно изменения массива. |
| Часовой пояс GMT +3, время: 16:55. |