34
30

More than 3 years have passed since last update.

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版を力技で使えるようにしているので不安定な部分が多く、注意です。

image.png

蛇足

react-native-hooksというライブラリも作られ始めているようですが、これはreact-nativeのmoduleにhooksをラッパーとして組み込んだもののようなので今回の記事では触れません!

そして

$ yarn start

いつもの画面がでました。

image.png

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を作ります。

Layout.js
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-baseInputIconで実装していきます。

AddTodoForm.js
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で実行します。

TodoList.js
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になります。

詳細: React.memoでrecomposeのpureが要らなくなった話 - Qiita

App.js

App.jsでカスタムuseStateをそれぞれのComponentに渡していきます。
useStateのラッパーをhooks/todoListに定義しています。

App.js
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);

用途別の関数を返すラッパーを定義します。

hooks/todolist.js
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));
    }
  };
};

そうすると・・・
2018-11-25_01.gif

reduxよりもサクッと作成できて、使いやすいですね。
native-baseを使って簡単にいい感じの見た目にできました。

refs

Special Thanks.
- Making a beautiful Todo App using React Hooks + Material UI

34
30
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
30