React Native:如何在从多个 rest api 中获取后动态渲染多个图像
React Native: How to render muliple images dynamically after fetching from multiple rest api's
我无法在 React Native 中渲染从多次调用 rest API 中获取的多张图像。
作为 Rest API 参考,我正在使用 woocommerce rest API 获取订单详细信息。
https://woocommerce.github.io/woocommerce-rest-api-docs/#retrieve-an-order
问题是订单详情在 API 中没有 line_items
的主要图像。因此,我需要通过再次调用产品详细信息 rest API.
调用以下每个产品详细信息 API 以通过 product_id
为每个 line_item 对象获取产品图像
https://woocommerce.github.io/woocommerce-rest-api-docs/#retrieve-a-product
到目前为止,我已经编写了为每个 line_item 调用产品详细信息的逻辑,但我的代码出现以下错误。处理这种情况的最佳方法是什么?
Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in %s.%s, the componentWillUnmount method,
下面是我的实现:
render() {
if (this.state.loading) {
return (
<View style={{ flex: 1, justifyContent: "center", alignContent: "center", padding: 20 }}>
<ActivityIndicator color='#96588a' size='large' />
</View>
)
}
return (
<ScrollView style={{ flex: 1 }}>
{this.displayOrderDataSection()}
{this.displayProductSection()}
{this.displayPaymentSection()}
{this.displayShippingDetailsSection()}
{this.displayBillingDetailsSection()}
</ScrollView>
);
}
getProductPrimaryImage = (productId) => {
let productData = null;
this.setState({ imageLoading: true });
let url = `${base_url}/wp-json/wc/v3/products/${productId}?consumer_key=${c_key}&consumer_secret=${c_secret}`
console.log(url);
fetch(url)
.then((response) => response.json())
.then((responseJson) => {
this.setState({
imageLoading: false,
error: responseJson.code || null,
});
productData = responseJson
})
.then(() => {
return productData ?
((Array.isArray(productData.images) && productData.images.length) ?
productData.images[0].src : null)
: null;
})
.catch((error) => {
this.setState({
error,
imageLoading: false,
})
});
}
getLineItems = () => {
let itemArray = [];
orderData.line_items.forEach(item => {
let imgSrc = this.getProductPrimaryImage(item.product_id)
itemArray.push(
<View key={item.id} style={{ flex: 1, flexDirection: 'row', backgroundColor: 'white' }}>
<View style={{ flex: 1, justifyContent: "center", alignContent: "center" }}>
<Image source={imgSrc}
style={{ height: 100, width: 100 }} resizeMode='contain' />
</View>
<View style={{ flex: 2, marginTop: 10, marginBottom: 10, justifyContent: "center" }}>
<View style={{ marginLeft: 10 }}>
<Text>{item.name}</Text>
<Text>SKU: {item.sku}</Text>
<Text>Price: {this.getCurrencySymbol()}{item.price.toFixed(2)}</Text>
<Text>Oty: {item.quantity}</Text>
<View>{this.getTMProductOptions(item.meta_data)}</View>
</View>
</View>
</View>
)
})
return itemArray;
}
displayProductSection = () => {
return (
<View style={styles.section}>
<Text style={styles.titleText}>Product</Text>
{this.getLineItems()}
</View>
)
}
考虑 render() 方法的方式是它可能 运行 重复。在大多数情况下,每当有影响其输出的更改时,它都会重新运行。
按照你的结构方式,你的 render() 函数调用 {this.displayProductSection()}
调用 this.getLineItems()
调用 this.getProductPrimaryImage(item.product_id)
向 WordPress AJAX 发出请求 API.
由于渲染可以(并且很可能会)重复 运行,这意味着您对图像的请求正在重复创建。
运行 AJAX 请求 不像显示图像那样 ,您将 src URL 放在标签和浏览器中加载一次。 HTML被解析一次,运行一次,这是重复请求了。
更好的模式是:
- 状态下,跟踪远程数据是否已经被请求。您可以使用 init、loading、success、error 作为可能的字符串来执行状态 属性。
- 在你的componentDidMount中,请求数据。将状态从 init 更改为 loading。
- 当数据进来时,将状态从loading改为success(或者error,取决于结果),并将结果存入state。
- 在您的渲染函数中,根据该状态执行条件 属性。如果正在加载,请显示加载器。如果成功,则根据上面存储的状态输出图像。
有时您还没有准备好在组件安装时获取远程数据。也许这首先取决于一些用户输入。在这种情况下,您可以改为连接到 componentDidUpdate。在那里检查你的情况,但由于 this 函数也重复调用 运行,还要检查状态,只有在尚未请求时才请求它。
无论哪种情况,请注意关注点分离。您的 render() 函数正在做一件事——显示。它不会启动网络请求或触发副作用。您的生命周期方法(或响应用户输入的函数)处理这些东西。
我非常感谢 tmdesigned 给我正确的指导。我从这个link.
中重新学习了react组件的概念
所以我通过将后续获取请求链接为 setState 中的回调来解决我的问题,setState 在 componentDidMount 中调用。下面是我的实现
componentDidMount() {
this.focusListener = this.props.navigation.addListener('didFocus', () => {
this.fetchOrderDetails()
});
}
fetchOrderDetails = () => {
const url = `${base_url}/wp-json/wc/v3/orders/${orderId}?consumer_key=${c_key}&consumer_secret=${c_secret}`;
this.setState({ loading: true });
fetch(url).then((response) => response.json())
.then((responseJson) => {
this.setState({
orderData: responseJson,
error: responseJson.code || null,
}, this.fetchOrderStatus())
}).catch((error) => {
this.setState({
error,
loading: false
})
});
}
fetchOrderStatus = () => {
const orderStatusesurl = `${base_url}/wp-json/wc/v3/reports/orders/totals?consumer_key=${c_key}&consumer_secret=${c_secret}`;
fetch(orderStatusesurl).then(response => response.json())
.then(responseJson => {
let orderStatusMap = new Map();
if (Array.isArray(responseJson) && responseJson.length > 0) {
if ('slug' in responseJson[0] && 'name' in responseJson[0]) {
responseJson.forEach(item => {
orderStatusMap.set(item.slug, item.name)
})
}
}
this.setState({
orderStatusOptions: [...orderStatusMap],
orderStatusValue: this.state.orderData.status,
loading: false,
}, this.fetchOrderProductImages())
})
}
fetchOrderProductImages = () => {
this.state.orderData.line_items.forEach((item, index) => {
this.fetchProductPrimaryImage(item.product_id, index)
})
}
fetchProductPrimaryImage = (productId, index) => {
this.setState({ imageLoading: true });
let url = `${base_url}/wp-json/wc/v3/products/${productId}?consumer_key=${c_key}&consumer_secret=${c_secret}`
fetch(url)
.then((response) => response.json())
.then(responseJson => {
if ('images' in responseJson && Array.isArray(responseJson.images) && responseJson.images.length) {
if ('line_items' in this.state.orderData && Array.isArray(this.state.orderData.line_items) && this.state.orderData.line_items.length) {
let modifiedOrderData = this.state.orderData
modifiedOrderData.line_items[index].primary_image_src = responseJson.images[0].src
this.setState({
orderData: modifiedOrderData,
imageLoading: false,
error: responseJson.code || null,
})
}
} else {
this.setState({
imageLoading: false,
error: responseJson.code || null,
});
}
})
.catch((error) => {
this.setState({
error,
imageLoading: false,
})
});
}
getLineItems = () => {
let itemArray = [];
this.state.orderData.line_items.forEach(item => {
itemArray.push(
<View key={item.id} style={{ flex: 1, flexDirection: 'row', backgroundColor: 'white' }}>
<View style={{ flex: 1, justifyContent: "center", alignContent: "center" }}>
<Image source={'primary_image_src' in item?{uri: item.primary_image_src}:null}
style={{ height: 100, width: 100 }} resizeMode='contain' />
</View>
<View style={{ flex: 2, marginTop: 10, marginBottom: 10, justifyContent: "center" }}>
<View style={{ marginLeft: 10 }}>
<Text>{item.name}</Text>
<Text>SKU: {item.sku}</Text>
<Text>Price: {this.getCurrencySymbol()}{item.price.toFixed(2)}</Text>
<Text>Oty: {item.quantity}</Text>
<View>{this.getTMProductOptions(item.meta_data)}</View>
</View>
</View>
</View>
)
})
return itemArray;
}
render() {
if (this.state.loading) {
return (
<View style={{ flex: 1, justifyContent: "center", alignContent: "center", padding: 20 }}>
<ActivityIndicator color='#96588a' size='large' />
</View>
)
}
return (
<ScrollView style={{ flex: 1 }}>
{this.displayOrderDataSection()}
{this.displayProductSection()}
{this.displayPaymentSection()}
{this.displayShippingDetailsSection()}
{this.displayBillingDetailsSection()}
</ScrollView>
);
}
我无法在 React Native 中渲染从多次调用 rest API 中获取的多张图像。
作为 Rest API 参考,我正在使用 woocommerce rest API 获取订单详细信息。 https://woocommerce.github.io/woocommerce-rest-api-docs/#retrieve-an-order
问题是订单详情在 API 中没有 line_items
的主要图像。因此,我需要通过再次调用产品详细信息 rest API.
product_id
为每个 line_item 对象获取产品图像
https://woocommerce.github.io/woocommerce-rest-api-docs/#retrieve-a-product
到目前为止,我已经编写了为每个 line_item 调用产品详细信息的逻辑,但我的代码出现以下错误。处理这种情况的最佳方法是什么?
Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in %s.%s, the componentWillUnmount method,
下面是我的实现:
render() {
if (this.state.loading) {
return (
<View style={{ flex: 1, justifyContent: "center", alignContent: "center", padding: 20 }}>
<ActivityIndicator color='#96588a' size='large' />
</View>
)
}
return (
<ScrollView style={{ flex: 1 }}>
{this.displayOrderDataSection()}
{this.displayProductSection()}
{this.displayPaymentSection()}
{this.displayShippingDetailsSection()}
{this.displayBillingDetailsSection()}
</ScrollView>
);
}
getProductPrimaryImage = (productId) => {
let productData = null;
this.setState({ imageLoading: true });
let url = `${base_url}/wp-json/wc/v3/products/${productId}?consumer_key=${c_key}&consumer_secret=${c_secret}`
console.log(url);
fetch(url)
.then((response) => response.json())
.then((responseJson) => {
this.setState({
imageLoading: false,
error: responseJson.code || null,
});
productData = responseJson
})
.then(() => {
return productData ?
((Array.isArray(productData.images) && productData.images.length) ?
productData.images[0].src : null)
: null;
})
.catch((error) => {
this.setState({
error,
imageLoading: false,
})
});
}
getLineItems = () => {
let itemArray = [];
orderData.line_items.forEach(item => {
let imgSrc = this.getProductPrimaryImage(item.product_id)
itemArray.push(
<View key={item.id} style={{ flex: 1, flexDirection: 'row', backgroundColor: 'white' }}>
<View style={{ flex: 1, justifyContent: "center", alignContent: "center" }}>
<Image source={imgSrc}
style={{ height: 100, width: 100 }} resizeMode='contain' />
</View>
<View style={{ flex: 2, marginTop: 10, marginBottom: 10, justifyContent: "center" }}>
<View style={{ marginLeft: 10 }}>
<Text>{item.name}</Text>
<Text>SKU: {item.sku}</Text>
<Text>Price: {this.getCurrencySymbol()}{item.price.toFixed(2)}</Text>
<Text>Oty: {item.quantity}</Text>
<View>{this.getTMProductOptions(item.meta_data)}</View>
</View>
</View>
</View>
)
})
return itemArray;
}
displayProductSection = () => {
return (
<View style={styles.section}>
<Text style={styles.titleText}>Product</Text>
{this.getLineItems()}
</View>
)
}
考虑 render() 方法的方式是它可能 运行 重复。在大多数情况下,每当有影响其输出的更改时,它都会重新运行。
按照你的结构方式,你的 render() 函数调用 {this.displayProductSection()}
调用 this.getLineItems()
调用 this.getProductPrimaryImage(item.product_id)
向 WordPress AJAX 发出请求 API.
由于渲染可以(并且很可能会)重复 运行,这意味着您对图像的请求正在重复创建。
运行 AJAX 请求 不像显示图像那样 ,您将 src URL 放在标签和浏览器中加载一次。 HTML被解析一次,运行一次,这是重复请求了。
更好的模式是:
- 状态下,跟踪远程数据是否已经被请求。您可以使用 init、loading、success、error 作为可能的字符串来执行状态 属性。
- 在你的componentDidMount中,请求数据。将状态从 init 更改为 loading。
- 当数据进来时,将状态从loading改为success(或者error,取决于结果),并将结果存入state。
- 在您的渲染函数中,根据该状态执行条件 属性。如果正在加载,请显示加载器。如果成功,则根据上面存储的状态输出图像。
有时您还没有准备好在组件安装时获取远程数据。也许这首先取决于一些用户输入。在这种情况下,您可以改为连接到 componentDidUpdate。在那里检查你的情况,但由于 this 函数也重复调用 运行,还要检查状态,只有在尚未请求时才请求它。
无论哪种情况,请注意关注点分离。您的 render() 函数正在做一件事——显示。它不会启动网络请求或触发副作用。您的生命周期方法(或响应用户输入的函数)处理这些东西。
我非常感谢 tmdesigned 给我正确的指导。我从这个link.
中重新学习了react组件的概念所以我通过将后续获取请求链接为 setState 中的回调来解决我的问题,setState 在 componentDidMount 中调用。下面是我的实现
componentDidMount() {
this.focusListener = this.props.navigation.addListener('didFocus', () => {
this.fetchOrderDetails()
});
}
fetchOrderDetails = () => {
const url = `${base_url}/wp-json/wc/v3/orders/${orderId}?consumer_key=${c_key}&consumer_secret=${c_secret}`;
this.setState({ loading: true });
fetch(url).then((response) => response.json())
.then((responseJson) => {
this.setState({
orderData: responseJson,
error: responseJson.code || null,
}, this.fetchOrderStatus())
}).catch((error) => {
this.setState({
error,
loading: false
})
});
}
fetchOrderStatus = () => {
const orderStatusesurl = `${base_url}/wp-json/wc/v3/reports/orders/totals?consumer_key=${c_key}&consumer_secret=${c_secret}`;
fetch(orderStatusesurl).then(response => response.json())
.then(responseJson => {
let orderStatusMap = new Map();
if (Array.isArray(responseJson) && responseJson.length > 0) {
if ('slug' in responseJson[0] && 'name' in responseJson[0]) {
responseJson.forEach(item => {
orderStatusMap.set(item.slug, item.name)
})
}
}
this.setState({
orderStatusOptions: [...orderStatusMap],
orderStatusValue: this.state.orderData.status,
loading: false,
}, this.fetchOrderProductImages())
})
}
fetchOrderProductImages = () => {
this.state.orderData.line_items.forEach((item, index) => {
this.fetchProductPrimaryImage(item.product_id, index)
})
}
fetchProductPrimaryImage = (productId, index) => {
this.setState({ imageLoading: true });
let url = `${base_url}/wp-json/wc/v3/products/${productId}?consumer_key=${c_key}&consumer_secret=${c_secret}`
fetch(url)
.then((response) => response.json())
.then(responseJson => {
if ('images' in responseJson && Array.isArray(responseJson.images) && responseJson.images.length) {
if ('line_items' in this.state.orderData && Array.isArray(this.state.orderData.line_items) && this.state.orderData.line_items.length) {
let modifiedOrderData = this.state.orderData
modifiedOrderData.line_items[index].primary_image_src = responseJson.images[0].src
this.setState({
orderData: modifiedOrderData,
imageLoading: false,
error: responseJson.code || null,
})
}
} else {
this.setState({
imageLoading: false,
error: responseJson.code || null,
});
}
})
.catch((error) => {
this.setState({
error,
imageLoading: false,
})
});
}
getLineItems = () => {
let itemArray = [];
this.state.orderData.line_items.forEach(item => {
itemArray.push(
<View key={item.id} style={{ flex: 1, flexDirection: 'row', backgroundColor: 'white' }}>
<View style={{ flex: 1, justifyContent: "center", alignContent: "center" }}>
<Image source={'primary_image_src' in item?{uri: item.primary_image_src}:null}
style={{ height: 100, width: 100 }} resizeMode='contain' />
</View>
<View style={{ flex: 2, marginTop: 10, marginBottom: 10, justifyContent: "center" }}>
<View style={{ marginLeft: 10 }}>
<Text>{item.name}</Text>
<Text>SKU: {item.sku}</Text>
<Text>Price: {this.getCurrencySymbol()}{item.price.toFixed(2)}</Text>
<Text>Oty: {item.quantity}</Text>
<View>{this.getTMProductOptions(item.meta_data)}</View>
</View>
</View>
</View>
)
})
return itemArray;
}
render() {
if (this.state.loading) {
return (
<View style={{ flex: 1, justifyContent: "center", alignContent: "center", padding: 20 }}>
<ActivityIndicator color='#96588a' size='large' />
</View>
)
}
return (
<ScrollView style={{ flex: 1 }}>
{this.displayOrderDataSection()}
{this.displayProductSection()}
{this.displayPaymentSection()}
{this.displayShippingDetailsSection()}
{this.displayBillingDetailsSection()}
</ScrollView>
);
}