昨日はExpo CLIを使ってGetting Started的なことをしてみましたが、今日はReact NativeなアプリにAlgoliaの検索を組み込んでみたいと思います。
expo startして、立ち上がった http://localhost:19002/ で Run on iOS simulator からiOSのエミュレーターを立ち上げてから、App.js を React InstantSearch | ⚡ Lightning-fast search for React and React Native を参考にしつつ↓こんな感じにしてみました。
1 import React from 'react';
2 import { StyleSheet, Text, View } from 'react-native';
3 import { InstantSearch } from 'react-instantsearch-native';
4
5 export default class App extends React.Component {
6 render() {
7 return (
8 <View style={styles.container}>
9 <InstantSearch
10 appId="latency"
11 apiKey="6be0576ff61c053d5f9a3225e2a90f76"
12 indexName="instant_search"
13 >
14 <Text>React Native Getting Started and Algolia Search!</Text>
15 </InstantSearch>
16 </View>
17 );
18 }
19 }
20
21 const styles = StyleSheet.create({
22 container: {
23 flex: 1,
24 marginTop: 20,
25 backgroundColor: '#fff',
26 alignItems: 'center',
27 justifyContent: 'center',
28 },
29 });
そうすると、コマンドラインではブワーっとそれっぽいログが。
[15:43:07] Finished building JavaScript bundle in 41ms.
[15:43:07] Finished building JavaScript bundle in 150ms.
[15:43:08] Finished building JavaScript bundle in 66ms.
[15:43:08] Finished building JavaScript bundle in 41ms.
[15:43:08] Finished building JavaScript bundle in 41ms.
[15:43:08] Finished building JavaScript bundle in 42ms.
[15:43:08] Finished building JavaScript bundle in 39ms.
[15:43:08] Finished building JavaScript bundle in 42ms.
[15:43:08] Finished building JavaScript bundle in 51ms.
[15:43:08] Finished building JavaScript bundle in 50ms.
[15:43:10] Running application "main" with appParams: {"rootTag":11,"initialProps":{"exp":{"manifest":{"splash":{"resizeMode":"contain","image":"./assets/splash.png","backgroundColor":"#ffffff","imageUrl":"http://127.0.0.1:19001/assets/./assets/splash.png"},"developer":{"projectRoot":"/Users/eshinoha/my-app","tool":"expo-cli"},"loadedFromCache":false,"privacy":"public","logUrl":"http://127.0.0.1:19000/logs","orientation":"portrait","sdkVersion":"32.0.0","mainModuleName":"node_modules/expo/AppEntry","env":{},"platforms":["ios","android"],"xde":true,"id":"@anonymous/my-app-a368c375-e241-411c-9dde-b57b39f80903","hostUri":"127.0.0.1:19000","iconUrl":"http://127.0.0.1:19001/assets/./assets/icon.png","assetBundlePatterns":["**/*"],"name":"My app","slug":"my-app","debuggerHost":"127.0.0.1:19001","icon":"./assets/icon.png","isVerified":false,"packagerOpts":{"lanType":"ip","dev":true,"minify":false,"urlRandomness":"ms-zmb","hostType":"lan"},"ios":{"supportsTablet":true},"updates":{"fallbackToCacheTimeout":0},"bundleUrl":"http://127.0.0.1:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false&assetPlugin=%2FUsers%2Feshinoha%2Fmy-app%2Fnode_modules%2Fexpo%2Ftools%2FhashAssetFiles.js","version":"1.0.0"},"initialUri":"exp://127.0.0.1:19000","appOwnership":"expo","shell":0}}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF
画面表示もイイ感じ。
続いて検索結果を表示していきます。Connectors というのを使うと、infinite scrollを実現できる、とのこと。今回はconnectInfiniteHitsというコネクタを使って実装していきます。こちらのコネクタにはhits, hasMore, refineというプロパティがあってhitsとhasMoreはまんまだけど、refineっていうのは一番最後まで読み切った時に呼ばれるファンクションらしい。
React Nativeの FlatList を使ってinfinite scrollを実現するそうですがReact Nativeがv0.43未満の場合はListViewを使う必要があるんだそうです。
んま、そんなこんなでコピペ前回でApp.jsを↓こんな風にしてみました。
1 import React from 'react';
2 import { StyleSheet, FlatList, Image, Text, View } from 'react-native';
3 import { InstantSearch, connectInfiniteHits } from 'react-instantsearch-native';
4
5
6 export default class App extends React.Component {
7 render() {
8 return (
9 <View style={styles.container}>
10 <InstantSearch
11 appId="latency"
12 apiKey="6be0576ff61c053d5f9a3225e2a90f76"
13 indexName="instant_search"
14 >
15 <Hits/>
16 </InstantSearch>
17 </View>
18 );
19 }
20 }
21
22 const Hits = connectInfiniteHits(({ hits, hasMore, refine }) => {
23 const onEndReached = function() {
24 if (hasMore) {
25 refine();
26 }
27 };
28 return (
29 <FlatList
30 data={hits}
31 onEndReached={onEndReached}
32 keyExtractor={(item, index) => item.objectID}
33 renderItem={({ item }) => {
34 return (
35 <View style={{ flexDirection: 'row', alignItems: 'center' }}>
36 <Image
37 style={{ height: 100, width: 100 }}
38 source={{ uri: item.image }}
39 />
40 <View style={{ flex: 1 }}>
41 <Text>
42 {item.name}
43 </Text>
44 <Text>
45 {item.type}
46 </Text>
47 </View>
48 </View>
49 );
50 }}
51 />
52 );
53 });
54
55 const styles = StyleSheet.create({
56 container: {
57 flex: 1,
58 marginTop: 20,
59 backgroundColor: '#fff',
60 alignItems: 'center',
61 justifyContent: 'center',
62 },
63 });
itemのimageだけで、nameやtypeが出てないのが激しく気になりますね…笑
そしてログを見ると、件数多過ぎじゃね?って感じなのかな、、汗
[16:43:12] VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc. Object {
[16:43:12] "contentLength": 6000,
[16:43:12] "dt": 1452,
[16:43:12] "prevDt": 1584,
[16:43:12] }
でもって↓を取り除いたら、
59 backgroundColor: '#fff',
60 alignItems: 'center',
61 justifyContent: 'center',
↓のようにそれっぽく表示されるようになりました。しかし、JSを更新するだけで自動でホットロードしてくれて、React Nativeお手軽だなぁ。
続いて、いよいよ検索窓を付けていきます。今回は connectSearchBox というコネクターを使いますよ、と。今回注目すべきパラメーターは2つあって、currentRefinementという現在のクエリと、refineという新しいクエリが入力された時に呼ばれるファンクション、とのことです。
ということで、importに諸々足して、render()の中でViewの定義追加して、その中にSearchBoxを突っ込んで、const SearchBox = connectSearchBox(({ refine, currentRefinement }) => { を追加して〜、みたいな感じで↓のように。
1 import React from 'react';
2 import { StyleSheet, FlatList, Image, Text, View, TextInput } from 'react-native';
3 import { InstantSearch, connectInfiniteHits, connectSearchBox } from 'react-instantsearch-native';
4
5
6 export default class App extends React.Component {
7 render() {
8 return (
9 <View style={styles.container}>
10 <InstantSearch
11 appId="latency"
12 apiKey="6be0576ff61c053d5f9a3225e2a90f76"
13 indexName="instant_search"
14 >
15 <View
16 style={{
17 flexDirection: 'row',
18 }}
19 >
20 <SearchBox />
21 </View>
22 <Hits/>
23 </InstantSearch>
24 </View>
25 );
26 }
27 }
28
29 const Hits = connectInfiniteHits(({ hits, hasMore, refine }) => {
30 const onEndReached = function() {
31 if (hasMore) {
32 refine();
33 }
34 };
35 return (
36 <FlatList
37 data={hits}
38 onEndReached={onEndReached}
39 keyExtractor={(item, index) => item.objectID}
40 renderItem={({ item }) => {
41 return (
42 <View style={{ flexDirection: 'row', alignItems: 'center' }}>
43 <Image
44 style={{ height: 100, width: 100 }}
45 source={{ uri: item.image }}
46 />
47 <View style={{ flex: 1 }}>
48 <Text>
49 {item.name}
50 </Text>
51 <Text>
52 {item.type}
53 </Text>
54 </View>
55 </View>
56 );
57 }}
58 />
59 );
60 });
61
62 const SearchBox = connectSearchBox(({ refine, currentRefinement }) => {
63
64 const styles = {
65 height: 60,
66 borderWidth: 1,
67 padding: 10,
68 margin: 10,
69 flex: 1,
70 };
71
72 return (
73 <TextInput
74 style={styles}
75 onChangeText={text => refine(text)}
76 value={currentRefinement}
77 placeholder={'Search a product...'}
78 clearButtonMode={'always'}
79 spellCheck={false}
80 autoCorrect={false}
81 autoCapitalize={'none'}
82 />
83 );
84 });
85
86 const styles = StyleSheet.create({
87 container: {
88 flex: 1,
89 marginTop: 20,
90 },
91 });
検索窓の位置が若干上過ぎる感はありますが、検索できるようになりました!
今度は検索結果にハイライトが出るようにしていきます。今回は connectHighligh というコネクタを使います。そしてHighlightというコンポーネント。そして、そろそろTerminalでvimでやりくりするのも疲れてきたので、VS Codeに切り替えます。
無事にconnectInfiniteHitsの中でHighlightを仕込んだところが黄色くハイライトされるようになりました。(↓のキャプチャ、インデントが微妙な件。笑)
続きはまた明日やっていきたいと思います 😀
amzn_assoc_ad_type =”responsive_search_widget”; amzn_assoc_tracking_id =”diary045-22″; amzn_assoc_marketplace =”amazon”; amzn_assoc_region =”JP”; amzn_assoc_placement =””; amzn_assoc_search_type = “search_widget”;amzn_assoc_width =”auto”; amzn_assoc_height =”auto”; amzn_assoc_default_search_category =””; amzn_assoc_default_search_key =”react native”;amzn_assoc_theme =”light”; amzn_assoc_bg_color =”FFFFFF”; //z-fe.amazon-adsystem.com/widgets/q?ServiceVersion=20070822&Operation=GetScript&ID=OneJS&WS=1&Marketplace=JP