Hooks 是javascript的函数, 他让我们可以使用state不需要写一个class
因此,Hooks 被用到了 react 函数组件,而不是class组件
请记住,Hooks文档中概述了我们需要遵循的规则,应该做的和不应该做的。文档地址:https://reactjs.org/docs/hooks-rules.html
想象一下序列的工作原理,或者也许您可以考虑数组如何工作以更好地理解它。
对于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”挂钩具有某些生命周期过程的相同目的,即componentDidMount,componentDidUpdate和componentWillUnmount,它在特定功能组件中执行其副作用。
我们首先调用D-I-Y“ API”,以使用“ useEffect”钩子获取数据。我们只希望Hook运行一次即可在初始渲染时获取第一组数据。因此,我们将空数组传递给第二个参数。
useEffect(() => {
requestToServer(page); // page => 1
}, []);
我们添加它来管理serverData状态的副作用。 看到,第二个参数现在由[serverData]提供,我们期望对serverData状态更改产生一些副作用:我们将它们与clientData合并以显示。
useEffect(() => {
setClientData([...clientData, ...serverData]);
}, [serverData]);
再次,我们建立另一个Hook来观察页面状态。页面更改后,我们再次从“服务器”请求数据。
useEffect(() => {
requestToServer(page);
}, [page]);
然后,“ handleLoadMore”函数用于处理页面更改。
const handleLoadMore = () => {
setPage(page + 1);
};
<FlatList
data={clientData}
renderItem={renderRow}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.1}
onRefresh={() => onRefresh()}
/>
在添加逻辑之后,我们具有类似于以下内容: 这里没有其他要注意的事情:
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;
*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进行了一些性能配置!