React Hooks on React Native
注意: v0.59.0-rc.1でhooksが使えるようになり、導入部分は古い内容になります。(2019-02-17)
Hooksでは、Function Conponent
でstateを扱えるようになりました。
useState
を使ったTodoアプリやらの記事が結構増えています。
そろそろ、React Native × Hooks でToDoアプリですよね!
github: nitaking/react-native-hooks-todo
Hooksについて
既に解説記事が多くありますので割愛しますが、
個人的に「sfcで組んでたけどstate使いたくなったからClass Componennt
にリファクタ...」という時間が減るのでいいですね。
React Hooksとは?useStateとかってなに?ってかたはこちら↓
環境構築
react/react-native
まずはinitします。
$ react-native init hooks_todo
2018/11/26時点ではreact nativeはhooksに対応していません。
使えるようにするには、reactのリポジトリからcloneしてゴニョゴニョして・・・と面倒です。
Using React Hooks in React Native · Issue #21967 · facebook/react-native
今回は有志の方がforkしたセットアップ済みreact-nativeを使っていきます!
https://github.com/facebook/react-native/issues/21967#issuecomment-438473603
$ yarn add react@next react-native@"npm:@brunolemos/react-native"
注意
うまくいかないときは以下のエラーが出ます。あくまでbeta版を力技で使えるようにしているので不安定な部分が多く、注意です。
蛇足
react-native-hooksというライブラリも作られ始めているようですが、これはreact-nativeのmoduleにhooksをラッパーとして組み込んだもののようなので今回の記事では触れません!
そして
$ yarn start
いつもの画面がでました。
native-base
今回は見え感を良くするためにnative-base
を使用します。
React Native版Bootstrapです。
$ yarn add native-base
$ react-native link
構成
今回の構成についてです。 refsの記事を参考にしています。
Component
- ToDoの入力
- ToDo一覧
<Layout>
<AddTodoForm />
<TodoList />
</Layout>
機能
- Todoの追加
- Todoのcheck/unCheck
- Todoの削除
今回はuseStateを使用し、それ拡張して実装していきます。
実装
<Layout>
<Header>
を持ったComponentを作ります。
import React, { memo } from 'react';
import { StyleSheet, View } from 'react-native';
import { Container, Header, Body, Title, Content } from "native-base";
const Layout = memo((props) => {
const { children } = props;
return (
<Container>
<Header>
<Body>
<Title>Hooks Todo List</Title>
</Body>
</Header>
<View style={styles.container}>
<Content style={styles.content}>
{children}
</Content>
</View>
</Container>
);
});
export default Layout;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
content: {
width: 350,
},
});
AddTodoForm
TextInputと入力ボタンを持つComponentを
native-base
のInput
とIcon
で実装していきます。
import React, { memo } from 'react';
import { Input, Item, Icon } from 'native-base';
const AddTodoList = memo((props) => {
const { inputValue, changeInput, onIconPress } = props;
return (
<Item success>
<Input
placeholder='Input todo...'
value={inputValue}
onChange={changeInput}
onChangeText={props.onInputKeyPress}
/>
<Icon type="Feather" name='plus' onPress={onIconPress} />
</Item>
);
});
export default AddTodoList;
TodoList
propsからtodoListを受け取って表示します。
- todo-list
- check/uncheck
- remove task
これらをuseStateの関数に定義し、propsを経由して受け取り、onPressで実行します。
import React, { memo } from 'react';
import { StyleSheet, FlatList } from 'react-native';
import { Text, ListItem, Left, Icon, Body, Right } from "native-base";
const TodoList = memo((props) => {
const { items, onItemCheck, onItemRemove } = props;
return (
<FlatList
data={items}
contentContainerStyle={styles.listView}
renderItem={({ item, index }) =>
<ListItem icon>
<Left>
<Icon
type="MaterialIcons"
style={styles.checkbox}
name={item.checked ? "check-box" : "check-box-outline-blank"}
onPress={() => onItemCheck(index)}
/>
</Left>
<Body>
<Text>{item.todoText}</Text>
</Body>
<Right>
<Icon
type="FontAwesome"
style={styles.icon}
name="trash-o"
onPress={() => onItemRemove(index)}
/>
</Right>
</ListItem>
}
/>
);
});
export default TodoList;
const styles = StyleSheet.create({
listView: {
flex: 1,
},
icon: {
color: 'grey',
},
checkbox: {
color: 'grey',
fontSize: 20,
}
});
React.memo?
-- const Component = (props) => {};
++ const Component = memo((props) => {});
Reactでは再レンダリングが多いとパフォーマンスが悪くなります。
shouldComponentUpdate
などでpropsが同じ場合に再レンダリングしないようにパフォーマンスチューニングを行うのですが、
Class ComponentにはshouldComponentUpdate
をデフォルトで行うReact.PureComponent
がありました。
Function Component
でこれを実装しているのがReact.memo
になります。
App.js
App.jsでカスタムuseStateをそれぞれのComponentに渡していきます。
useStateのラッパーをhooks/todoList
に定義しています。
import React, { memo } from 'react';
import Layout from "./component/Layout";
import TodoList from "./component/TodoList";
import AddTodoForm from "./component/AddTodoForm";
import { useInputValue, useTodos } from "./hooks/todoList";
const App = memo((props) => {
const { inputValue, changeInput, clearInput } = useInputValue();
const { todos, addTodo, checkTodo, removeTodo } = useTodos();
const clearInputAndAddTodo = _ => {
clearInput();
addTodo(inputValue);
};
return (
<Layout>
<AddTodoForm
inputValue={inputValue}
changeInput={changeInput}
onIconPress={clearInputAndAddTodo}
/>
<TodoList
items={todos}
onItemCheck={idx => checkTodo(idx)}
onItemRemove={idx => removeTodo(idx)}
/>
</Layout>
);
});
export default App;
custom hooks
ここでは以下のuseStateだけでは足りない用途を補うため、
const [state, setState] = useState(initialState);
用途別の関数を返すラッパーを定義します。
import { useState } from 'react';
export const useInputValue = (initialValue = '') => {
const [inputValue, setInputValue] = useState(initialValue);
return {
inputValue,
changeInput: event => {
setInputValue(event.nativeEvent.text)},
clearInput: () => setInputValue(''),
};
};
export const useTodos = (initialValue = []) => {
const [todos, setTodos] = useState(initialValue);
return {
todos,
addTodo: todoText => {
if (todoText !== '') {
setTodos(todos.concat({ todoText, checked: false }));
}
},
checkTodo: checkIndex => {
setTodos(
todos.map((todo, index) => {
if (checkIndex === index) todo.checked = !todo.checked;
return todo;
})
)
},
removeTodo: removeIndex => {
setTodos(todos.filter((todos, index) => removeIndex !== index));
}
};
};
reduxよりもサクッと作成できて、使いやすいですね。
native-baseを使って簡単にいい感じの見た目にできました。
refs
Special Thanks.