20190227のReactに関する記事は4件です。

Reactを学び始める前に持っていた5つの誤解

2週間ほど前からReactを学び始め、理解するほど増していく面白さにどハマり中です。しかし始める前は取っつきづらい印象があったのも確かでしたし、今までと全く異なる世界観に踏み込むのは勇気が要ると思うので、Reactを始める前に持っていた「なんとなく取っつきづらい」という印象を忘れないうちに言語化しておこうと思います。

※初心者プログラマーの個人の見解です。

誤解1.JSXがキモい

確かにぱっと見はキモいですよね。
でもなぜそんなキモい見た目になっているんでしょうか?

const listItem = () => {
  return (
    <li>
      リストアイテム
    </li>
  )
}

それは、この例で言うと
「処理の途中にDOM要素がある」
のではなく、この関数が
「DOMの設計図を返す関数」
だからです。

DOMの設計図とか言われても...という人はクッキー(焼くほう)の型を思い浮かべてみてください。

const BakedCookie = () => {
  return (
    <i class="fas fa-cookie"></i>
  )
}
<BakedCookie />
<BakedCookie />
<BakedCookie />
<BakedCookie />
<BakedCookie />

スクリーンショット 2019-02-27 20.29.47.png

1.クッキーの型(設計図)を作る
2.同じ型でクッキー(実物)を作る
3.たくさんクッキーが焼ける

定義した関数名をHTMLのタグのように書くだけでクッキーがたくさん焼けました。
Reactはこのコンポーネントをツリー状に構成してページを作っていきます。
こう考えると、ページを再利用可能なコンポーネントの組み合わせで構築しようとした場合、非常に合理的で見た目も直感的だと思いませんか?

誤解2.関数型プログラミングに習熟していないと扱えない

コンポーネント(DOMの設計図)は入力によって出力が一意に決まる関数であるとか、状態を管理するためにセットで使われることが多いReduxの単方向データフローとか、Reactの思想や基盤が関数型的であるのは事実ですが、関数型プログラミングやその考え方に習熟していなくてもReactのルールに従っていればある程度関数型な設計になるのかな、と言う印象です。(良い書き方/悪い書き方をしっかり確認して遵守する必要はあります)
公式でもゴリゴリ関数的型な書き方はされていませんし、「関数型」というワードに怯える必要はあまりないと感じました。

誤解3.知らない用語が多くて異世界っぽい

propsとかstateとか知らない単語がたくさん出てきますが、特に新しい概念でもなくReactにおいてそういう名前なんだという程度の認識でOKだと思います。

Props.jsx
// propsはコンポーネントへの引数のようなもの
const CustomButton = props => {
  return (
    <button>
    {/* タグの中ではインライン式みたいな感じで使える */}
    {props.buttonText}
    </button>
  );
};
Props.jsx
// props.buttonText = "Purchase" 的なイメージ
<CustomButton buttonText="Purchase" />
State.jsx
// stateはコンポーネントのグローバル変数のようなもの
class CustomButton extends Component {
  render() {
    return (
      // setStateで入力時に文字をstateに格納している(直接代入してはいけない)
      // 入力した値をそのままvalueにせず、stateを通すことでバリデーションとかがやりやすくなる
      <input type="text" value={this.state.textVal} onChange={e => this.setState({ textVal: e.target.value })} />
    );
  }
};

要は何も難しくないよってことです。

誤解4.そもそも何のために使うのかわからない

これに関しては完璧にまとめあげられている記事があります。
というかこれを見ておおおすげえ...!となって始めたところもあります。笑
https://qiita.com/naruto/items/fdb61bc743395f8d8faf

誤解5.環境構築がめんどくさそう

npm install create-react-app
create-react-app myFirstReactApp

以上!閉廷!
この後npm startすると開発用サーバーが起動して、ソースを更新すると自動でトランスパイルしてリロードしてくれる親切設計になっています。

まとめ

React興味はあるけどなんか難しそうだしめんどくさそうだし...と思っている人に、その理由がここで挙げたようなものであればそんなことないよというのが伝われば幸いです。
ちなみに学習にはUdemyのこのコースを使っています。相手の立場に立つってこういうことなんだなあ(悟り)と違う意味で学ぶことがあるくらいオススメです(回し者ではありません)
https://www.udemy.com/react-redux/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React + TypeScript】コンポーネントのdefaultPropsの型をきちんと指定する

環境情報

  • TypeScript: 3.2.2
  • React: 16.7.0

前提知識: デフォルトプロパティの定義

Class Componentにおけるデフォルトプロパティを定義する際は、下記の通りdefaultPropsフィールドを用意してあげます。

class SomeComponent extends React.Component<SomeProps, SomeState> {
    public static defaultProps: SomeProps = {
        prop1: "default"
    };
    ...
}

サンプルコンポーネント

今回説明を行うに当たって下記のようなButtonコンポーネントを考えてみましょう。
ButtonコンポーネントはプロパティとしてonClick, color, typeを保持しています。

Button.tsx
interface Props {
  onClick: (e: React.MouseEvent<HTMLElement>) => void;
  color: "blue" | "green" | "red";
  type: "button" | "submit";
}

class Button extends React.Component<Props, {}> {
  /**
   * Render DOM.
   */
  public render() {
    return <button type={this.props.type} style={{ color: this.props.color }} onClick={this.props.onClick} />;
  }
}

このコンポーネントはデフォルトプロパティを定義していないため、Buttonコンポーネントを利用する側がonClick, color, type属性を指定しないとコンパイルエラーとなります。

App.tsx
class App extends React.Component {
  public render() {
    // Type error: Type '{}' is missing the following properties from type 'Readonly<InputProps>': onClick, color, type  TS2739
    return <Button />;
  }
}

デフォルトプロパティを定義

全プロパティのデフォルト値を定義する場合は下記のようにすればOKです。

Button.tsx
interface Props {
  onClick?: (e: React.MouseEvent<HTMLElement>) => void;
  color?: "blue" | "green" | "red";
  type?: "button" | "submit";
}

class Button extends React.Component<Props, {}> {
  /**
   * Default properties.
   */
  public static defaultProps: Props = {
    onClick: _ => console.log("clicked"),
    color: "blue",
    type: "button",
  };
  ...
}

これでButtonコンポーネントを利用する側がonClick, color, type属性を指定しない場合はデフォルト値が適用されるため、コンパイルエラーになることはありません。

App.tsx
class App extends React.Component {
  public render() {
    // OK
    return <Button />;
  }
}

一部のプロパティのみデフォルト値を定義

先ほどは全プロパティのデフォルト値を定義していましたが、一部のプロパティのみデフォルト値を定義したい場合はどうすればいいでしょうか。例えばonClickプロパティはデフォルト値を定義せず、外部から必ず指定させたいというケースです。

defaultPropsの型にPropsを指定しているため、一部の項目のみを定義することはできません。

Button.tsx
interface Props {
  onClick: (e: React.MouseEvent<HTMLElement>) => void;
  color?: "blue" | "green" | "red";
  type?: "button" | "submit";
}

class Button extends React.Component<Props, {}> {
  /**
   * Default properties.
   */
  // Type error: Property 'onClick' is missing in type '{ color: "blue"; type: "button"; }' but required in type 'Props'.  TS2741
  public static defaultProps: Props = {
    color: "blue",
    type: "button",
  };
  ...
}

対応案1: defaultPropsの型をPartialにする

当初はdefaultPropsの型をPartial<Props>とすることで部分的なProps型を指定してあげれば、解決すると思ってました。

Button.tsx
interface Props {
  onClick: (e: React.MouseEvent<HTMLElement>) => void;
  color?: "blue" | "green" | "red";
  type?: "button" | "submit";
}

class Button extends React.Component<Props, {}> {
  /**
   * Default properties.
   */
  public static defaultProps: Partial<Props> = {
    color: "blue",
    type: "button",
  };
  ...
}

しかしPartialを利用すると、onClick属性の型が(e: React.MouseEvent<HTMLElement>) => void | undefinedとなってしまい、onClick属性を指定しなかった場合にコンパイルエラーとならず、予期せぬ実行時エラーを招く結果となります。

App.tsx
class App extends React.Component {
  public render() {
    // コンパイルエラーとならず、onClickにはundefinedが設定されてしまう
    return <Button />;
  }
}

対応案2: defaultPropsの型を明示的に定義せず型推論させる

defaultPropsの型として明示的にPropsを指定していましたが、これをやめ型推論を利用してみます。

Button.tsx
interface Props {
  onClick: (e: React.MouseEvent<HTMLElement>) => void;
  color?: "blue" | "green" | "red";
  type?: "button" | "submit";
}

class Button extends React.Component<Props, {}> {
  /**
   * Default properties.
   */
  public static defaultProps = {
    color: "blue",
    type: "button",
  };
  ...
}

onClick属性を指定しない場合は、コンパイルエラーとなることが確認できます。

App.tsx
class App extends React.Component {
  public render() {
    // Type error: Property 'onClick' is missing in type '{}' but required in type 'Pick<Readonly<{ children?: ReactNode; }> & Readonly<Props>, "children" | "onClick">'.  TS2741
    return <Button />;
  }
}

しかしこの場合、defaultPropsにどんなプロパティでも定義できてしまうため、TypeScriptの恩恵に与ることができません。うっかりタイポしてしまった場合でもコンパイルエラーで気づくことができないのです。

Button.tsx
interface Props {
  onClick: (e: React.MouseEvent<HTMLElement>) => void;
  color?: "blue" | "green" | "red";
  type?: "button" | "submit";
}

class Button extends React.Component<Props, {}> {
  /**
   * Default properties.
   */
  public static defaultProps = {
    color: "blue",
    types: "button", // タイポに気づくことができない
    hoge: "hoge", // 任意のプロパティを追加できてしまう
  };
  ...
}

ではどうすればいいでしょうか。

対応案3: defaultProps用のinterfaceを定義する

defaultProps用のinterfaceを明示的に用意してあげればOKです。

Button.tsx
interface Props {
  onClick: (e: React.MouseEvent<HTMLElement>) => void;
  color?: "blue" | "green" | "red";
  type?: "button" | "submit";
}

interface DefaultProps {
  color: "blue" | "green" | "red";
  type: "button" | "submit";
}

class Button extends React.Component<Props, {}> {
  /**
   * Default properties.
   */
  public static defaultProps: Partial<DefaultProps> = {
    color: "blue",
    type: "button",
  };
  ...
}

onClick属性を指定しない場合は、コンパイルエラーとなることが確認できました。

App.tsx
// Type error: Property 'onClick' is missing in type '{}' but required in type 'Pick<Readonly<{ children?: ReactNode; }> & Readonly<Props>, "children" | "onClick">'.  TS2741
class App extends React.Component {
  public render() {
    return <Button />;
  }
}

ただし、このままではPropsDefaultPropsの定義が重複しておりメンテナンス性が悪いため、PropsDefaultPropsを継承するようにします。

Button.tsx
interface Props extends Partial<DefaultProps> {
  onClick: (e: React.MouseEvent<HTMLElement>) => void;
}

interface DefaultProps {
  color: "blue" | "green" | "red";
  type: "button" | "submit";
}

class Button extends React.Component<Props, {}> {
  /**
   * Default properties.
   */
  public static defaultProps: Partial<DefaultProps> = {
    color: "blue",
    type: "button",
  };
  ...
}

これでばっちりです。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自作アプリにJest+enzyme導入

?初心者です

前回導入編を紹介したので、更にdispatchした時やTextFieldに入力された時のバリデーションテスト等々を、自分用の備忘録がてら紹介していこうと思います。

(…まだまだエラーに躓きっ放しでございます!???)

■ TextFieldに入力した文字が280文字を超えたらエラーを表示させる

今実装しているのが、「280文字を超えたらエラーを表示させる」テキストエリアです。下のように、Material-UIのTextFieldで用意されているerrorhelperTextのプロパティを使ってエラーを表示させるようにしました。

import TextField from '@material-ui/core/TextField'

class Form extends React.Component {
  state = {
    status: '',
    statusError: null,
  }
  handleChange = event => {
    const text = event.target.value
    this.setState({ status: text }, () => {
      this.validateStatus()
    })
  }
  validateStatus = () => {
    this.setState({
      statusError:
        this.state.status.length > 280
          ? '文字数は280文字以内に収めてください'
          : null,
    })
  }

  render() {
    return (
      <div>
        <TextField
          label="Let's Tweet!"
          placeholder="280文字以内で入力してください"
          margin="none"
          variant="filled"
          rows="5"
          multiline
          fullWidth
          onChange={this.handleChange}
          value={this.state.status}
          error={this.state.statusError ? true : false}
          helperText={
            this.state.statusError ? this.state.statusError : null
          }
        />
      </div>
    )
  }
}

TextFieldhandleChangeを設置して、stateにあるstatusに入れています。this.setState()の引数に280文字以上超えた時のバリデーションを用意しました。ユーザーがリアルタイムで入力してエラーが表示されるようにしています。

ちゃんと280文字超えたらerrortrueになってくれるか、テストしていきます。下はテストです。Jestとenzymeを使っています!
ちなみにこの

import { storeFactory } from '@app/testUtils/testUtils'

は前回記事で書いたファイルです。→記事

import * as React from 'react'
import { Provider } from 'react-redux'
import { storeFactory } from '@app/testUtils/testUtils'
import { createShallow, createMount } from '@material-ui/core/test-utils'
import Form from '@app/components/Form'
import TextField from '@material-ui/core/TextField'

const shallow = createShallow()
const mount = createMount()

const setup = (state = {}) => {
  const store = storeFactory(state)
  const wrapper = mount(
    <Provider store={store}>
      <Tweet />
    </Provider>
  )
  return wrapper
}

describe('<Tweet />', () => {
  it('Textareaが280文字の時、エラーメッセージは表示されない', () => {
    const wrapper = setup()
    wrapper
      .find(TextField)
      .find('textarea')
      .simulate('change', {
        target: {
          value:
            'あなたは翌日もしその紹介人というのの日にしましたい。はなはだ今朝をお話し人はとうとう同じ意見たありかもをしているんがは相違炙っございでて、一応には防ぐませたたまし。モーニングをするでしょのもちょうど将来を無論ないなます。いやしくも岡田さんへ開始権力まだ存在へ怒らです平気その責任皆か運動のというご意見ありましだでして、その同年はそれか文芸先生をできるば、嘉納さんののへ道の誰がもっと小ごろごろとさので私仕立をご合点の教えようにもうご始末をふらしますならから、はなはだいよいよ講演を買い占めるですからかかるたのに倒さなた。なおそれでお自我にするつもりもいっそ好き',
        },
      })

    expect(wrapper.find(TextField).props().error).toBe(false)
  })

  it('Textareaが281文字の時、エラーメッセージを表示する', () => {
    const wrapper = setup()
    wrapper
      .find(TextField)
      .find('textarea')
      .simulate('change', {
        target: {
          value:
            'あなたは翌日もしその紹介人というのの日にしましたい。はなはだ今朝をお話し人はとうとう同じ意見たありかもをしているんがは相違炙っございでて、一応には防ぐませたたまし。モーニングをするでしょのもちょうど将来を無論ないなます。いやしくも岡田さんへ開始権力まだ存在へ怒らです平気その責任皆か運動のというご意見ありましだでして、その同年はそれか文芸先生をできるば、嘉納さんののへ道の誰がもっと小ごろごろとさので私仕立をご合点の教えようにもうご始末をふらしますならから、はなはだいよいよ講演を買い占めるですからかかるたのに倒さなた。なおそれでお自我にするつもりもいっそ好きと',
        },
      })

    expect(wrapper.find(TextField).props().error).toBe(true)
  })
})

wrapper.find(TextField)を繋げ、さらにその中にあるtextareaを探します。(今回はTextFieldmultilineを入れてテキストエリアにしています!テキストフォームの場合は.find(input)です。)
テキストエリアが入力されたイベントはenzymeで用意されているsimulateを使っています。
最後にTextFieldのerrorプロパティはwrapper.find(TextField).props().errorのように指定してあげます。この記述で正しく動作することを確認できました!

2.png

▼ エラー解消してくれた記事

Testing with Jest and Enzyme in React — Part 3 (Best Practices when testing with Jest and Enzyme)

見ればわかるテストなので笑、Submitした時dispatchされるかテストしていきます!

■ Submitした時、dispatchされることを確認する

上記でエラーメッセージが正しく表示されることを確認できました!
お次は、バリーデーションに引っかかっている状態の時はSubmitされては困るので、それもテストします。
- 0字の時はSubmitされない
- 1字以上280字以内の時、Submitされる
- 280字を超えたらSubmitされない

今回は、Submitされた時this.props.postTweetStatusが動作するように書いています。

const setup = (state = {}) => {
  const props = {
    postTweetStatus: jest.fn()
  }
  const store = storeFactory(state)
  const wrapper = mount(
    <Provider store={store}>
      <Tweet {...props} />
    </Provider>
  )
  return { wrapper, props }
}

describe('dispatchの動作テスト', () => {
  it('0文字の時dispatchされない', () => {
    const { wrapper, props } = setup()
    const submitButton = wrapper
      .find(`[data-test="submitButton"]`)
      .at(0)
      .find('button')
    expect(props.postTweetStatus).not.toHaveBeenCalled()
  })

  it('0字以上280文字以内の時正しくdispatchされる', () => {
    const { wrapper, props } = setup()
    const submitButton = wrapper
      .find(`[data-test="submitButton"]`)
      .at(0)
      .find('button')
    wrapper.setState({ status: 'test' })

    submitButton.simulate('click', () => {
      const { wrapper, props } = setup()
      const submitButton = wrapper
        .find(`[data-test="submitButton"]`)
        .at(0)
        .find('button')
      expect(props.postTweetStatus).toHaveBeenCalled()
    })
  })

  it('281字以上の時dispatchされない', () => {
    const { wrapper, props } = setup()
    const submitButton = wrapper
      .find(`[data-test="submitButton"]`)
      .at(0)
      .find('button')
    wrapper.setState({ status: 'あなたは翌日もしその紹介人というのの日にしましたい。はなはだ今朝をお話し人はとうとう同じ意見たありかもをしているんがは相違炙っございでて、一応には防ぐませたたまし。モーニングをするでしょのもちょうど将来を無論ないなます。いやしくも岡田さんへ開始権力まだ存在へ怒らです平気その責任皆か運動のというご意見ありましだでして、その同年はそれか文芸先生をできるば、嘉納さんののへ道の誰がもっと小ごろごろとさので私仕立をご合点の教えようにもうご始末をふらしますならから、はなはだいよいよ講演を買い占めるですからかかるたのに倒さなた。なおそれでお自我にするつもりもいっそ好きと' })

    submitButton.simulate('click', () => {
      expect(props.postTweetStatus).not.toHaveBeenCalled()
    })
  })
})

setup()のところにActionで書いている関数をjest.fn()で設定してあげます。
それをwrapperのコンポーネントタグで{...props}のように渡してあげて前準備は完成です!

const props = {
  postTweetStatus: jest.fn()
}

wrapper.setState({ ... })でwrapperのローカルステートを更新しています。
ダミーテキストは適当に入れちゃいましたがもっと細かくできるかも(??)
そのあとにsimulateでクリックさせた上で予想している動作を書いてあげます。

submitButton.simulate('click', () => {
  // テスト
})

参考にしたYoutube → Mocking Axios in Jest + Testing Async Functions

テスト結果はオールパス!!??
ここに行き着くまでもかなり時間掛かっています…!

3.png

■ 記事を取得後、記事のlength分ループして記事が表示されている

今までstoreを使う必要があったのでmountで対応していましたが、このテストでは上手くいきませんでした…
mountでテストしようとすると、読み込んでいる子コンポーネントの値が取れないとエラーが表示されてしまうのです…!?? なので今回はshallowの方を使い、逆にコンポーネント側をテストに合わせて少しだけ作り変えています。

記事一覧で実装したテストは3点です。
- 新規登録した時や取得する記事が0件の時、デフォルト表示用に用意したdivタグの表示を確認
- ローディング時はローディングだけのdivが表示
- 記事取得後、記事のlengthと表示のループ回数が一緒

詳細を省きますが、コンポーネントの方はこのように実装しました。

ローディング

propsで渡ってくるtimeline.loadingがtrueの時、<CircularProgress />を表示させています。タグにはテスト用のタグ、data-test="loadingView"を付与しました。

記事が0件の時(例えば初回ログイン時の時など)

props.timeline.homeTimeline.lengthの記事がイコール0の時、data-test="firstSignUpView"を付与してあるdivタグを表示させます。

エラー

timeline.errorがtrueの時、data-test="errorView"を付与したdivを表示させます。

記事取得成功

data-test="timlineView"を付与したdivを表示するようにしています。

export class TimelineLists extends React.Component {
  render() {
    const { timeline } = this.props

    if (timeline.loading) {
      return <CircularProgress data-test="loadingView" />
    } else if (timeline.homeTimeline.length === 0) {
      return (
        <div data-test="firstSignUpView">
          <p>早速記事を投稿してみましょう!</p>
          <Button>記事を投稿する</Button>
        </div>
      )
    } else if (timeline.error) {
      return (
        <div data-test="errorView">
          取得に失敗しました
          <Button>再取得する</Button>
        </div>
      )
    }
    return (
      <div data-test="timlineView">
        {timeline.homeTimeline.map(article => {
          return (
            <div key={article.id} data-test="articleView">
              <TimelineArticleMedia article={article} />
            </div>
          )
        })}
      </div>
    )
  }
}
export default TimelineLists

結構省いてしまいましたが、Reduxを使っているのでconnectも指定しているし、ページ遷移用のボタンもあったので、react-router-domも読み込ませています。

テストがしやすいように、classの前にexportを付けました。

export class TimelineLists extends React.Component {

実際のテストがこちらです。

import * as React from 'react'
import { createShallow, createMount } from '@material-ui/core/test-utils'
import { TimelineLists } from '@app/components/TimelineLists'

const shallow = createShallow()
const mount = createMount()

const initialProps = {
  timeline: {
    homeTimeline: [],
    loading: false,
    error: null
  },
}

const setup = (props = initialProps) => {
  const wrapper = shallow(<TimelineLists {...props} />)
  return wrapper
}

describe('<TimelineLists />', () => {
  describe('各ステータス表示テスト', () => {
    it('新規登録時または記事0件の時、"firstSignUpView"が表示される', () => {
      const wrapper = setup()
      expect(wrapper.find(`[data-test="firstSignUpView"]`).exists()).toBe(true)
    })

    it('ローディング時"loadingViewが表示される', () => {
      const props = {
        timeline: {
          homeTimeline: [{ id: 1 }, { id: 2 }, { id: 3 }],
          loading: true,
          error: null,
          initialTimelineLoad: false
        },
      }
      const wrapper = setup({}, props)

      expect(wrapper.find(`[data-test="loadingView"]`).exists()).toBe(true)
    })

    it('記事一覧取得成功時、記事のlengthと同じループ処理がされる', () => {
      const props = {
        timeline: {
          homeTimeline: [{ id: 1 }, { id: 2 }, { id: 3 }],
          loading: false,
          error: null,
          initialTimelineLoad: false,
        },
      }
      const wrapper = setup({}, props)

      expect(wrapper.find(`[data-test="articleView"]`)).toHaveLength(
        props.timeline.homeTimeline.length
      )
    })
  })
})

テストしたい状態をconst props = {}の中で再現し、setup()関数の引数に渡しています。
it(() => {})それぞれのpropsを変えてテストしています。
今回は存在の確認だったので、enzymeが用意しているexists()のtrue / falseで確認しています!

4.png

長くなったので、今回はここで締めることにします。次回もテストについて!?

【やってみて】
絶っっ対他に方法がある…と確信しています。
冗長的だし、より複雑なテストをするとなると今のコードでは太刀打ち出来ないかも?
もっと深掘りしていきます。

【終わりに】
今携わっている案件で、バグの対応を依頼されました。
エラーを見ると共通ヘッダーコンポーネントでエラーが出ている様子…
ほぼ全コンポーネントでヘッダー読み込ませているので、例えば1箇所変更してもどこがどう変わっちゃうのか分かりません…!
スナップショットテストを導入したら、その話をQiitaに載せようと思います。

話は脱線しますが、このQiita記事を書いている間に、VSCodeのテーマを変更しました♫
今のテーマは「Dracula Official」!!!?‍♂️
ピンクが可愛い!!!!

(こちらの記事を参考にVSCodeのプラグインを入れたり消したりしました。The Ultimate VSCode Setup for Front End/JS/React

エラー画面を見ても盛り下がらない工夫必要w

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自主勉強継続するためにGitHub使ってタスク管理をやった話

自主学習を継続したい!!!!!!

 優秀なプログラマーは自分で最新の情報を仕入れて、プライベートでもコード書いているっていうよね。かっこいいなあ。私も優秀なプログラマーになりたいなー。とふわっと考えて、作りたいもの練って見るんだけど、凡人プログラマー未満の癖にやけに壮大なことを考えるせいで、大体アイデアだけで飽きる。時々、環境作ろうとはするものの、どうせならやったことない言語で描こうとして、完璧な環境構築しようとした結果、HelloWorldで満足してしまう。なんとか、継続できないかと悩んだ苦肉の策の話です。

目次

・初めに
・GitHubにプロジェクトを作る
・やりたいことをissueに書き出す
・ひとまずdevelopブランチを切る
・僅かにでも進捗得たらコミット
・issue単位でpush&pullrequest
・まとめ

初めに

 タグはほぼ詐欺です。私がたまたまチョイスしたのがReact、redux、ついでに言うと、firebaseでホームページを作ろうとしていただけです。環境構築方法については、他の方が非常にわかりやすくまとめているのでそちらを参考にしてください。
React + redux + typescript
React + Redux + Firebase
 今回、タスク管理のために、社内で行なっているアジャイル開発のノウハウを取り入れてみました。ベンチャー企業の行う理想的なアジャイル開発とは異なるため、「こんなのアジャイルじゃない!!」と言う方も多々いると思いますがご容赦ください。また、ここで紹介する方法は、3日坊主の方や、やりたいことが壮大になっちゃうんだけどすぐに飽きちゃうという、多少ぐうたらな人向けに書いています。タスク管理なんてできて当然と言う方は、私はぐうの音も出ないため戻った方が効率的です。ただし、gitについての知識はある前提になります。

GitHubにプロジェクトを作る

 はいタイトルにあるようにGitHubを使います。アカウント無い人は作成してください。で、とりあえずプロジェクトを作成します。ここまではまあ調べてください。
そしたらcloneしましょう

git clone https://github.com/xxxxx/xxxxxxxx.git

 作成したら使用する言語を決めて、ローカル環境で一番最初の作業 だけやったら、buildできることだけ確認してpushしてしまいましょう。私は初回はmasterにpushしてしまいました。直接が嫌な方はbranch切ってやってください。
スクリーンショット 2019-02-26 23.15.36.png

私はReactを使いたかったので、

create-react-app

コマンドで環境構築した時点でpushしてしまいました。

やりたいことをissueに書き出す

 はい。面倒臭がりな私ですが、無駄に凝り性なので、やりたいことをissueに書きます。私の粒度は下記のような感じでした
スクリーンショット 2019-02-26 23.19.54.png

で、issueの中に実際にやることを書きます
スクリーンショット 2019-02-26 23.22.15.png

スクリーンショット 2019-02-26 23.21.28.png

書いてみると、割とやること多いんですよね。そこでアジャイルの思想が出てくるのですが、基本的に完成版をイメージしてはいけません。あくまでも直近でやるべきことから書いていきます。私の場合は環境構築で挫折することが多い人間なので、環境構築ステップを一通り洗った時点で一度issueの洗い出しをやめました。

issue作成のポイントをまとめると
・作るものを決める
・作成のためのステップを出来るだけ細かくissueにする
・issueは作りすぎない

といった感じになります。issueをたくさん作りすぎると、こんなにたくさんできないやんけとなって、挫折するので、機能を実現できる最小単位で作るのがベストです。

ひとまずdevelopブランチを切る

 gitなら作業はmasterからブランチ切るっしょ。と言うことで、ひとまずdevelopに切ります。

git checkout -b develop

 今回は作業者が私だけなのでdevelopとmasterブランチしかありませんが、複数人で作業する場合は都度branchを切ってください。ただ、今回はソロでのお勉強なので2つあれば十分だと思います。そうしたら、issueの中で最も重要だと思うものを1つ選択して、実装に取り掛かります。

僅かにでも進捗得たらコミット

 スキあらばcommitしましょう。buildが通ればcommit。UIの文言変わったらcommit。Qiitaで参考にしたコードコピペしたらcommitくらいの感じで。私は変更箇所が100行超えないように心がけています。
 人によっては、commitの単位も機能単位にした方が良いと言う方もいると思います。それはおっしゃる通りですが、勉強を継続するには、commitは細かいに越したことは無いと思います(異論は認める)。例えば、reactのbootstrapを導入した時は、以下のレベルでcommitしました。(下記は、react-bootstrapのサンプルをコピっただけ)
スクリーンショット 2019-02-26 23.39.52.png

issue単位でpush&pullrequest

 動く単位でcommitを繰り返したら、リモートにpushしましょう

git push origin develop

そしたら、pullrequestを作成しましょう。(GitHubのbranchの横にあります。)
スクリーンショット 2019-02-26 23.52.34.png

developからmasterに向けてのpullrequestを作成します。(私はpackage.lock.jsonでconflict起きちゃいましたが)
ここで、pullrequestに関連するissueを本文に追加しておくとタスク管理がかなり楽になります。
スクリーンショット 2019-02-26 23.56.08.png

pullrequestを作成したら、スカッシュマージしてしまいましょう。(赤いのはレビュー通らないとマージできない設定入れているからです。通常は緑)
スクリーンショット 2019-02-27 0.01.34.png

スカッシュマージは、▼から選べます。基本的にスカッシュを推奨します。スカッシュマージは、pullrequestでマージする全てのcommitをpullrequestのタイトルcommit1つにまとめてくれるため、historyがすっきりします。また、コミット内容が確認したければ、githubのinsight > networkから、commitを辿ることで細かい修正も確認できます。
スクリーンショット 2019-02-27 0.07.12.png

スクリーンショット 2019-02-27 0.03.26.png

またpullrequestにissueの番号を入れておくことで、issueの方にも関連が持てます。(pullrequestで#3を説明部分に記載した時)
スクリーンショット 2019-02-27 0.12.40.png

明示的にissueが完了したことがわかるので、issueはcloseしちゃいましょう
スクリーンショット 2019-02-27 0.14.42.png

あとは、developにブランチを切るところからの繰り返し

まとめ

 タスク管理ツールなんていくらでもあるので適材適所だとは思いますが、今回はgithubだけで管理してみました。長い目で機能を考えると大抵挫折するので、アジャイルに細々としたリリースを心がけ、なおかつ直近にできるタスクをissue化して実装を繰り返していった方が、個人での実装なら良いのでは無いかと。
 まあ、本来のissueの使い方では無いと思いますが、私はこんな使い方をしてみましたと言う共有でした。

もっと便利に運用できるぜってコメントがあればぜひご教授ください。(本来の使い方じゃ無いぜ。とか、よくみたら変なcommit入っているやんけとかの意見もあると思いますが、勉強中の身ゆえ許してください)

# 補足
こんなふわっとしたやり方じゃダメでしょって人は、もっと丁寧に書いてくれた人がいました
https://qiita.com/suzuki-hoge/items/3a568dff36fd981082ba
ただ、個人の勉強レベルで期限決めたりテンプレ作ったりはその時点でめんどくさくなっちゃうので、これくらいなら一人でも継続できそうだなってレベルで今回の記事は書きました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む