Skip to content

2017-chu-kmgs4524 #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 186 additions & 19 deletions List.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,79 +3,246 @@ import {
FlatList,
View,
Text,
Alert,
} from 'react-native';
import ListItem from './ListItem';

export default class List extends Component {
constructor(props) {
super(props);
this.state = {
// 預設載入
//預設 loading
isRefreshing: true,
data: [],
page: 0
page: 1,
rows: [],
};
}

// componentDidMount 載入後抓第一筆資料
getRow = () => {
let rows = [];

for(let i = 1; i <= 10; i++){
rows.push({ title: `標題${i}`, key: i });
}
return rows
}

// componentDidMount()不是在render後才執行嗎,為什麼不把this.setState()放在componentWillMount呢?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

這部分可以參考這篇喔
https://daveceddia.com/where-fetch-data-componentwillmount-vs-componentdidmount/
簡單來說主要有兩個原因

  1. 在等待 fetch callback 的時候是是沒有資料的,如果沒有正確設定 state 空狀態就會造成crash,可以避免本地測試的時候網路太快,瞬間載完資料沒有測到的情況
  2. 在 React 有 serverside render 所以 WillMount 會被叫兩次,但是 RN 沒有這困擾,所以大家放 DidMount 也已經習慣且變成慣例了

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

好的 感謝解答~

// 第一次 render 後抓第一筆資料
async componentDidMount() {
await this.getData(0)
this.setState({
data: await this.getData(this.state.page)
})
}

// 整理資料
format = (array) => {
return array.map((data) => {
return {
// 整理資料格式符合 ListItem props
key: data.id,
name: data.name,
job: data.job_title,
avatar: data.avatar
}
})
}

getData = async(page) => {
try {
// 這裡要記得改成自己電腦的 IP
const IP ='192.168.2.101';
const IP ='192.168.88.1';
console.log(IP)
// 可以使用的 API
// http://${IP}:1337/pokemons/1
// http://${IP}:1337/users/1
// http://192.168.88.1:1337/users/1
let response = await fetch(`http://${IP}:1337/users?_page=${page}&_limit=10`);
let responseJson = await response.json();
console.log('responseJson', responseJson);
const data = this.format(responseJson);
if (page === 0) {
// console.log('responseJson', responseJson);
let data = this.format(responseJson);
console.log('data in getData', data);
if (page === 1) {
// 第一筆資料,記得關掉 loading
this.setState({
isRefreshing: false
})
} else {
// 滾動加載更新資料
// 滾動載入資料
}
return responseJson;
// return responseJson; 應該是要return data吧?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

這邊我沒注意到XDD
其實本意是要在 if 裡面直接做 set data 的動作 不用 return 東西

// console.log('data', data);
return data;
} catch (e) {
console.error(e);
}
}

componentWillMount() {
// This approach has a major flaw. If you call setState twice before the render cycle,
// the second call will clobber the first call.

// for(let i = 1; i <= 3; i++){
// let nextItem = this.state.rows.slice();
// nextItem.push({ title: `標題${i}`, key: i });
// this.setState({
// rows: nextItem
// });
// console.log(`第${i}次`, nextItem, this.state.rows);
// }

// this.setState({
// rows: this.getRow()
// });
}



// 保持 render() 的純粹,不要在裡面進行 state 修改或是使用非同步方法
render() {
//console.log('This.state.rows: ', this.state.rows);
return (
<FlatList

data={
// 資料
[{ title: 'title' }, { title: 'title2' },{ title: 'title3' }]
// [{ title: 'title' }, { title: 'title2' },{ title: 'title3' }, ]
//[<ListItem />, <ListItem />, <ListItem />, <ListItem />, <ListItem />]
this.state.data
}

renderItem={({item}) => {
// return 剛剛實作的 ListItem
return <Text>{item.title}</Text>
}}
onEndReached={() => {
// 滑到底部的時候加載新資料
// return <Text>{item.title}</Text>
return <ListItem key={item.key} title={item.name} desc={item.job} image={item.avatar}/>
}}

//initialNumToRender={10}

// onEndReachedThreshold={0.03}
// onEndReached={({ distanceFromEnd }) => {
// // 滑到底部的時候載入新資料
// Alert.alert('distanceFromEnd', distanceFromEnd.toString());
// const p1 = new Promise((resolve, reject) => {
// this.setState({
// page: this.state.page + 1,
// })
// console.log('page', this.state.page);
// });

// p1.then(() => {
// this.setState({
// data: this.state.data.concat(this.getData(this.state.page))
// });
// console.log('data', this.state.data);
// }, function(reason) {
// console.log(reason);
// })


// }}

extraData={this.state}

refreshing={this.state.isRefreshing}
onRefresh={() => {
// 下拉刷新

// 下拉刷新
onRefresh= { () => {

let nextData = this.state.data;
console.log('length before concat', nextData.length);

// 錯誤的方式2
// wait = async() => {
// return new Promise((resolve, reject) => {
// get = () => (
// nextData.concat(this.getData(2))
// )
// resolve(get());
// }
// )
// }
// nextData = await wait();

// update = () => {
// nextData = await wait();
// }
// update();
const p1 = new Promise((resolve, reject) => {
this.setState({
page: this.state.page + 1
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setState 其實是有自己的 callback 的

this.setState({
   page: this.state.page + 1
}, () => {
  // 這裡是 setState 完成會呼叫的 callback
})

resolve(this.state.page);
});

p1.then(async(newPage) => {
try{
this.setState({
data: this.state.data.concat(await this.getData(this.state.page)) //關鍵在於必須先取得非同步getData()的資料
Copy link
Contributor

@FuYaoDe FuYaoDe Aug 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

這邊這樣子資料是不會更新的,因為 concat 是用參考的方式,不是新的 array,這樣下次 render 值又會變回原來的,要使用這樣

const data = await this.getData(this.state.page);
this.setState({
  data: [...this.state.data, data]
})

來合併

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我打data: [...this.state.data, data]會變成[ { key: 10,
name: 'Bowie Wathey',
job: 'Information Systems Manager',
avatar: 'https://robohash.org/eaquenobisenim.png?size=150x150&set=set1' }, [ { key: 11,
name: 'Benito O'Heaney',
job: 'Chemical Engineer',
avatar: 'https://robohash.org/autemeosquia.png?size=150x150&set=set1'}, ... ] ]。
data似乎也要加上... ,把新加入data的value迭代出來結果才會正確。像這樣:data: [...this.state.data, ...data],不知是否有錯?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

嗯嗯 對 data 的 array 也要解構

})
console.log('state.data', this.state.data);
// nextData = nextData.concat(await this.getData(2))
// console.log('nextData', nextData);
} catch(err) {
console.log('Error', err);
}
});

// 正確方式
// fetchNewData = async() => {
// try{
// this.setState({
// data: this.state.data.concat(await this.getData(this.state.page)) //關鍵在於必須先取得非同步getData()的資料
// })
// console.log('state.data', this.state.data);
// // nextData = nextData.concat(await this.getData(2))
// // console.log('nextData', nextData);
// } catch(err) {
// console.log('Error', err);
// }
// }
// fetchNewData();

// 錯誤方式1
// const p1 = new Promise((resolve, reject) => {

// });

// p1.then((value) => {
// console.log('nextData.length', value.length);
// }, (err) => {
// console.log('Error', err);
// })


// try{
// nextData = await nextData.concat(this.getData(this.state.page + 1));
// if(nextData.length > 10) {
// console.log('nextData', nextData);
// } else{

// }
// } catch(err){
// console.log('Error', err)
// }

// p1.then((data) => {
// console.log('nextData', data);
// })

// this.setState({
// data: nextData
// })
//console.log(this.state.data);
}}

ItemSeparatorComponent={({highlighted}) => {
// return 簡單的分隔線
return null;
// return null;
return <View style={{ height: 1, backgroundColor: 'lightgray' }} />
}}
/>
);
}

}
37 changes: 30 additions & 7 deletions ListItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
Text,
Image,
TouchableOpacity,
TouchableNativeFeedback,
} from 'react-native';

export default class ListItem extends Component {
Expand All @@ -14,6 +15,7 @@ export default class ListItem extends Component {
image: PropTypes.string,
onPress: PropTypes.func,
}
// Prop 預設值,若對應的 props 沒傳入值將會使用 default 值
static defaultProps = {
title: '標題',
desc: '內容',
Expand All @@ -26,14 +28,22 @@ export default class ListItem extends Component {

render() {
return (
// TouchableOpacity: 點擊後變透明的按鈕
<TouchableOpacity style={styles.container} onPress={this.props.onPress}>
<Image
style={{ height: 80, width: 80 }}
source={{ uri: this.props.image }}
/>
<Text>
{this.props.title}
</Text>
<View>
<Image
style={{ height: 80, width: 80, borderRadius: 50, borderWidth: 2.5, borderColor: 'lightgray'}}
source={{ uri: this.props.image }}
/>
</View>
<View style={styles.titleContainer}>
<Text style={styles.title}>
{this.props.title}
</Text>
<Text style={styles.desc}>
{this.props.desc}
</Text>
</View>
</TouchableOpacity>
);
}
Expand All @@ -47,4 +57,17 @@ const styles = StyleSheet.create({
padding: 10,
backgroundColor: '#eee',
},
titleContainer: {
flexDirection: 'column',
padding: 15,
paddingLeft: 30
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: 'gray'
},
desc: {

}
})
2 changes: 1 addition & 1 deletion index.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import List from './List';
export default class sample1 extends Component {
render() {
return (
<ViewSample />
<List />
);
}
}
Expand Down