react-nativeは簡単だ。react nativeのライブラリを読んでみた。　reac-native-progress編

react-nativeと聞くと、また新しい技術好きなフロントエンジニアが、負の遺産を世界遺産のように吹聴していると眉をしかめる人がいる。
そんなことはない。react nativeは2018年で一番学習コストが低く、モバイルアプリを作る方法の一つだ。(reduxを使わないとする)
これはjsをこれまで書いたことのない人にも当てはまる。
習熟したreactエンジニアなら一週間。jsエンジニアなら一ヶ月ほどで、javaとswiftを三ヶ月学んで作ったアプリを越えることができる。

今回、react nativeの開発で使うライブラリを読み解いてみたい。というのも、react nativeのライブラリは実は、標準的なjsで書かれていることが多く、javaやswiftをあまり必要としないのだ。

この記事を機にreact nativeを少しでも触ってくれる方がいれば幸いである。


## 今回やること
react-native-progressという、ゲームでローディングの待ち時間を示す以下のような表示を実装するプラグインのBarを読んでいきたい。


<img width="504" alt="スクリーンショット 2018-08-04 0.27.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/183835/91a4a074-ea14-9350-b7df-ec81be675c69.png">

まず、全容を示します。と言っても200行なので軽く目を通します。

```react
// Bar
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Animated, Easing, View } from 'react-native';

const INDETERMINATE_WIDTH_FACTOR = 0.3;
const BAR_WIDTH_ZERO_POSITION =
  INDETERMINATE_WIDTH_FACTOR / (1 + INDETERMINATE_WIDTH_FACTOR);

export default class ProgressBar extends Component {
  static propTypes = {
    animated: PropTypes.bool,
    borderColor: PropTypes.string,
    borderRadius: PropTypes.number,
    borderWidth: PropTypes.number,
    children: PropTypes.node,
    color: PropTypes.string,
    height: PropTypes.number,
    indeterminate: PropTypes.bool,
    onLayout: PropTypes.func,
    progress: PropTypes.number,
    style: PropTypes.any,
    unfilledColor: PropTypes.string,
    width: PropTypes.number,
    useNativeDriver: PropTypes.bool,
    // eslint-disable-next-line react/forbid-prop-types
    animationConfig: PropTypes.object.isRequired,
    animationType: PropTypes.oneOf(['decay', 'timing', 'spring']),
  };

  static defaultProps = {
    animated: true,
    borderRadius: 4,
    borderWidth: 1,
    color: 'rgba(0, 122, 255, 1)',
    height: 6,
    indeterminate: false,
    progress: 0,
    width: 150,
    useNativeDriver: false,
    animationConfig: { bounciness: 0 },
    animationType: 'spring',
  };

  constructor(props) {
    super(props);
    const progress = Math.min(Math.max(props.progress, 0), 1);
    this.state = {
      width: 0,
      progress: new Animated.Value(
        props.indeterminate ? INDETERMINATE_WIDTH_FACTOR : progress
      ),
      animationValue: new Animated.Value(BAR_WIDTH_ZERO_POSITION),
    };
  }

  componentDidMount() {
    if (this.props.indeterminate) {
      this.animate();
    }
  }

  componentWillReceiveProps(props) {
    if (props.indeterminate !== this.props.indeterminate) {
      if (props.indeterminate) {
        this.animate();
      } else {
        Animated.spring(this.state.animationValue, {
          toValue: BAR_WIDTH_ZERO_POSITION,
          useNativeDriver: props.useNativeDriver,
        }).start();
      }
    }
    if (
      props.indeterminate !== this.props.indeterminate ||
      props.progress !== this.props.progress
    ) {
      const progress = props.indeterminate
        ? INDETERMINATE_WIDTH_FACTOR
        : Math.min(Math.max(props.progress, 0), 1);

      if (props.animated) {
        const { animationType, animationConfig } = this.props;
        Animated[animationType](this.state.progress, {
          ...animationConfig,
          toValue: progress,
          useNativeDriver: props.useNativeDriver,
        }).start();
      } else {
        this.state.progress.setValue(progress);
      }
    }
  }

  animate() {
    this.state.animationValue.setValue(0);
    Animated.timing(this.state.animationValue, {
      toValue: 1,
      duration: 1000,
      easing: Easing.linear,
      isInteraction: false,
      useNativeDriver: this.props.useNativeDriver,
    }).start(endState => {
      if (endState.finished) {
        this.animate();
      }
    });
  }

  handleLayout = event => {
    if (!this.props.width) {
      this.setState({ width: event.nativeEvent.layout.width });
    }
    if (this.props.onLayout) {
      this.props.onLayout(event);
    }
  };

  render() {
    const {
      borderColor,
      borderRadius,
      borderWidth,
      children,
      color,
      height,
      style,
      unfilledColor,
      width,
      ...restProps
    } = this.props;

    const innerWidth = Math.max(0, width || this.state.width) - borderWidth * 2;
    const containerStyle = {
      width,
      borderWidth,
      borderColor: borderColor || color,
      borderRadius,
      overflow: 'hidden',
      backgroundColor: unfilledColor,
    };
    const progressStyle = {
      backgroundColor: color,
      height,
      transform: [
        {
          translateX: this.state.animationValue.interpolate({
            inputRange: [0, 1],
            outputRange: [innerWidth * -INDETERMINATE_WIDTH_FACTOR, innerWidth],
          }),
        },
        {
          translateX: this.state.progress.interpolate({
            inputRange: [0, 1],
            outputRange: [innerWidth / -2, 0],
          }),
        },
        {
          // Interpolation a temp workaround for https://github.com/facebook/react-native/issues/6278
          scaleX: this.state.progress.interpolate({
            inputRange: [0, 1],
            outputRange: [0.0001, 1],
          }),
        },
      ],
    };

    return (
      <View
        style={[containerStyle, style]}
        onLayout={this.handleLayout}
        {...restProps}
      >
        <Animated.View style={progressStyle} />
        {children}
      </View>
    );
  }
}
```

それでは解説していきましょう。上のコードをみながらどうぞ。

## part1
static propType
static defaultProps
何も難しいところはありません。
よくある標準的なproptypeの使い方です。

## part2
constructor(props)
よくあるconstructorですね
途中でAnimatedという単語が出てきますが、これはreact native備え付けのアニメーション機能です。
これも標準的なreactですね。

## part3
componentDidMount
componentWillReceiveProps
なんということでしょう標準的なreactです。
何も言うことはありませんね。

## part4
animate()
ここでやっとreactNativeの機能であるanimationを使い始めます。
しかし見てください。
react nativeを知らなくてもなんとなく、どう動くか分かりませんか？

## part5
render()
ここはもう普通のreactですね。


こんなクソ記事を書いてしまってすみません。ただ私が言いたいことは一つです。
reactからvueへ行こうとしてるあなた。vueの代わりにreact native勉強してみませんか？
vueと比べて学習コストを抑えて動くものがサクサク作れますよ。

以上酔っ払いのreact native布教活動でした。
終わり。