Hooks 是javascript的函數, 他讓我們可以使用state不需要寫一個class
因此,Hooks 被用到了 react 函數組件,而不是class組件
請記住,Hooks文檔中概述了我們需要遵循的規則,應該做的和不應該做的。文檔地址:https://reactjs.org/docs/hooks-rules.html
- 不要在循環或嵌套函數中調用Hook,更不要說在條件邏輯中了
- 在您的React函數開始的最高點處執行Call Hooks。
理由
想象一下序列的工作原理,或者也許您可以考慮數組如何工作以更好地理解它。
對於React中的渲染過程,將爲每個渲染以完全相同的順序調用Hook。如果弄亂了渲染中調用的順序,則可能導致數據不一致。並且這種情況是禁止的。
如果您有興趣確保可以在其他地方找到一些相關的文章,我們可能會在本文中通過unpacking how Hooks work主題。
首先…鉤子用例! 規則,還記得嗎?
1.在功能組件而不是類中使用它們…
2.在函數開始的最頂部調用它們。
const ItemList = props => { const [limit, setLimit] = useState(5);
const [page, setPage] = useState(1);
const [clientData, setClientData] = useState([]);
const [serverData, serverDataLoaded] = useState([]);
const [pending_process, setPending_process] = useState(true);
const [loadmore, setLoadmore] = useState(false);
}
讓我們設置一個虛擬數據庫,如下所示: 這就是我們將用來複制從API調用返回的響應的功能。
const itemList = [ {id: '1', name: 'Item!'},
{id: '2', name: 'Item@'},
{id: '3', name: 'Item#'},
{id: '4', name: 'Item$'},
{id: '5', name: 'Item%'},
{id: '6', name: 'Item^'},
{id: '7', name: 'Item&'},
{id: '8', name: 'Item*'},
{id: '9', name: 'Item('},
{id: '10', name: 'Item)'},
{id: '11', name: 'Item!!'},
{id: '12', name: 'Item@@'},
{id: '13', name: 'Item##'},
{id: '14', name: 'Item$$'},
{id: '15', name: 'Item%%'},
{id: '16', name: 'Item^^'},
{id: '17', name: 'Item&&'},
{id: '18', name: 'Item**'},
{id: '19', name: 'Item(('},
{id: '20', name: 'Item))'}
];
我們爲API調用添加了一些模擬功能,如下所示:
// delay for 1500ms to simulate a API request time.
// we start with page = 1.
// itemList is a constants dataset as show above.
const ApiRequest = async thePage => {
await setTimeout(() => {}, 1500);
return itemList.slice((thePage - 1) * limit, thePage * limit);
};
const requestToServer = async thePage => {
let data = await ApiRequest(thePage);
serverDataLoaded(data);
};
然後,我們將API響應數據設置爲狀態變量“ serverData”。
接下來,我們繼續進行“ useEffect”掛鉤。
“ useEffect”掛鉤具有某些生命週期過程的相同目的,即componentDidMount,componentDidUpdate和componentWillUnmount,它在特定功能組件中執行其副作用。
我們首先調用D-I-Y“ API”,以使用“ useEffect”鉤子獲取數據。我們只希望Hook運行一次即可在初始渲染時獲取第一組數據。因此,我們將空數組傳遞給第二個參數。
useEffect(() => {
requestToServer(page); // page => 1
}, []);
我們可以在一個功能組件中使用多個useEffect Hooks。
我們添加它來管理serverData狀態的副作用。 看到,第二個參數現在由[serverData]提供,我們期望對serverData狀態更改產生一些副作用:我們將它們與clientData合併以顯示。
useEffect(() => {
setClientData([...clientData, ...serverData]);
}, [serverData]);
再次,我們建立另一個Hook來觀察頁面狀態。頁面更改後,我們再次從“服務器”請求數據。
useEffect(() => {
requestToServer(page);
}, [page]);
然後,“ handleLoadMore”函數用於處理頁面更改。
const handleLoadMore = () => {
setPage(page + 1);
};
其次……FlatList用例! 基本FlatList的外觀如下:
<FlatList
data={clientData}
renderItem={renderRow}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.1}
onRefresh={() => onRefresh()}
/>
在添加邏輯之後,我們具有類似於以下內容: 這裏沒有其他要注意的事情:
- onEndReachedThreshold | number
- onEndReached | function
- onRefresh | function
- pending_process | state
- loadmore | state
第三……動畫API! 請參見下面的Animated.FlatList的框架:
import { Animated } from 'react-native';
const ItemList = props => {
...
const [scrollY, setScrollY] = useState(new Animated.Value(0));
const [op, setOp] = useState(
scrollY.interpolate({
inputRange: [0, 50, 100, 150],
outputRange: [1, 0.5, 0.25, 0]
}));
...
const renderRow = ({item, index, separators}) => {
return (
<Animated.View style={{opacity: op}}>
<ListItem
style={{
height: ITEM_HEIGHT,
backgroundColor: Constants.COLOR.GOLD
}}>
<Text style={{color: 'white'}}>{item.name}</Text
</ListItem>
</Animated.View>
)
}
return (
<Animated.FlatList
onScroll={Animated.event(
[
{
nativeEvent: {contentOffset: {y: scrollY}},
},
],
{
useNativeDriver: true,
listener: handleScroll
},
)}
/>
);
}
scrollY:新的Animated.Value(0) 被定義爲用作動畫實例,起始值爲0。
scrollY.interpolate({inputRange,outputRange}) 是相對於我們期望的滾動Y軸[inputRange]的不透明度[outputRange]的變化樣式。
我們將添加邏輯。以及新動畫值的一些計算。最終內容將類似於……
import React, {useState, useEffect, useRef} from 'react';
import {View, Animated, Dimensions, Text} from 'react-native';
import {ListItem} from 'native-base';
import {_} from 'lodash';
import generalStyles from '../../generalStyles';
import Constants from '../../Constants';
const {height} = Dimensions.get('window');
const ITEM_HEIGHT = height * 0.2;
const itemList = [
{id: '1', name: 'Item!'},
{id: '2', name: 'Item@'},
{id: '3', name: 'Item#'},
{id: '4', name: 'Item$'},
{id: '5', name: 'Item%'},
{id: '6', name: 'Item^'},
{id: '7', name: 'Item&'},
{id: '8', name: 'Item*'},
{id: '9', name: 'Item('},
{id: '10', name: 'Item)'},
{id: '11', name: 'Item!!'},
{id: '12', name: 'Item@@'},
{id: '13', name: 'Item##'},
{id: '14', name: 'Item$$'},
{id: '15', name: 'Item%%'},
{id: '16', name: 'Item^^'},
{id: '17', name: 'Item&&'},
{id: '18', name: 'Item**'},
{id: '19', name: 'Item(('},
{id: '20', name: 'Item))'},
];
const ItemListDemo = props => {
const flatListRef = useRef(null);
const [limit, setLimit] = useState(5);
const [page, setPage] = useState(1);
const [clientData, setClientData] = useState([]);
const [serverData, serverDataLoaded] = useState([]);
const [pending_process, setPending_process] = useState(true);
const [loadmore, setLoadmore] = useState(false);
const [refresh, setRefresh] = useState(false);
const [fadingIndex, setFadingIndex] = useState(0);
const [scrollDirection, setScrollDirection] = useState(false);
const [offsetY, setOffsetY] = useState(0);
const [scrollY, setScrollY] = useState(new Animated.Value(0));
const [op, setOp] = useState(
scrollY.interpolate({
inputRange: [0, 50, 100, 150],
outputRange: [1, 0.5, 0.25, 0],
}),
);
const ApiRequest = async thePage => {
await setTimeout(() => {}, 1500);
return itemList.slice((thePage - 1) * limit, thePage * limit);
};
const requestToServer = async thePage => {
let data = await ApiRequest(thePage);
console.log('data', data);
serverDataLoaded(data);
};
useEffect(() => {
console.log('requestToServer');
requestToServer(page);
}, []);
useEffect(() => {
console.log('obtained serverData', serverData);
if (serverData.length > 0) {
setRefresh(false);
setClientData([...clientData, ...serverData]);
setLoadmore(serverData.length == limit ? true : false);
setPending_process(false);
} else {
setLoadmore(false);
}
}, [serverData]);
useEffect(() => {
console.log('load more with page', page);
if (serverData.length == limit || page == 1) {
setPending_process(true);
requestToServer(page);
}
}, [page]);
const handleLoadMore = () => {
console.log('loadmore', loadmore);
console.log('pending_process', pending_process);
if (loadmore && !pending_process) {
setPage(page + 1);
}
};
const onRefresh = () => {
setClientData([]);
setPage(1);
setRefresh(true);
setPending_process(false);
};
const renderRow = ({item, index, separators}) => {
return (
<Animated.View
style={
index < fadingIndex && scrollDirection == 'down' ? {opacity: op} : {}
}>
<ListItem
style={{
height: ITEM_HEIGHT,
backgroundColor: Constants.COLOR.GOLD,
}}>
<Text style={{color: 'white'}}>{item.name}</Text>
</ListItem>
</Animated.View>
);
};
const handleScroll = event => {
let {contentSize, contentOffset} = event.nativeEvent;
let h = ITEM_HEIGHT;
let reachingIndex = Math.ceil(contentOffset.y / h);
setOffsetY(contentOffset.y);
setScrollDirection(contentOffset.y > offsetY ? 'down' : 'up');
let in_range = [0, h / 3, (h / 3) * 2, h];
let out_range = [1, 0.5, 0.25, 0];
let out_range_new = [];
let in_range_new = [];
let d = h / 3;
for (i = 0; i <= reachingIndex; i++) {
out_range_new = _.concat(out_range_new, out_range);
_.each(in_range, (val, index) => {
let n = in_range_new.length + 1;
let next_num = in_range[0] + (n - 1) * d;
in_range_new.push(next_num);
});
}
console.log('out_range_new', out_range_new);
console.log('in_range_new', in_range_new);
setOp(
scrollY.interpolate({
inputRange: in_range_new,
outputRange: out_range_new,
}),
);
setFadingIndex(reachingIndex);
};
const getItemLayout = (data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
});
return (
<View style={[generalStyles.container, generalStyles.bg_white]}>
<Animated.FlatList
onScroll={Animated.event(
[
{
nativeEvent: {contentOffset: {y: scrollY}},
},
],
{
useNativeDriver: true,
listener: handleScroll,
},
)}
getItemLayout={getItemLayout}
refreshing={refresh}
data={clientData}
renderItem={renderRow}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.1}
onRefresh={() => onRefresh()}
/>
</View>
);
};
export default ItemListDemo;
是的!最後……我們將爲FlatList進行一些優化配置!
*1. Use getItemLayout*
*2. Apply keyExtractor props to FlatList*
*3. Avoid feeding anonymous function to “renderItem” props*
4. Use useMemo Hooks to replace “shouldComponentUpdate”
import React, {useState, useEffect, useMemo} from 'react';
import { Dimensions } from 'react-native';
const {height} = Dimensions.get('window');
const ITEM_HEIGHT = height * 0.25;
const ItemListDemo = props => {
const keyExtractor = (item, index) => item.id;
const getItemLayout = (data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index
});
return useMemo(() => {
return (
<FlatList
keyExtractor={keyExtractor}
data={clientData}
renderItem={renderRow}
getItemLayout={getItemLayout}
/>
);
}, [clientData, fadingIndex]);
};
}
結論:
我們從本文的第一部分學到了React Hooks的基礎知識,並且在隨後的部分中我們還獲得了一些使用useState和useEffect Hooks的動手編程經驗。 其次,我們使用按需數據提取邏輯從頭開始運行FlatList,然後使用Animated API來控制列表項的不透明樣式。 最後,我們還對React Native中的FlatList進行了一些性能配置!