- 投稿日:2019-02-27T22:08:30+09:00
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 />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/
- 投稿日:2019-02-27T16:59:40+09:00
【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.tsxinterface 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.tsxclass 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.tsxinterface 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.tsxclass App extends React.Component { public render() { // OK return <Button />; } }一部のプロパティのみデフォルト値を定義
先ほどは全プロパティのデフォルト値を定義していましたが、一部のプロパティのみデフォルト値を定義したい場合はどうすればいいでしょうか。例えば
onClick
プロパティはデフォルト値を定義せず、外部から必ず指定させたいというケースです。
defaultProps
の型にProps
を指定しているため、一部の項目のみを定義することはできません。Button.tsxinterface 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.tsxinterface 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.tsxclass App extends React.Component { public render() { // コンパイルエラーとならず、onClickにはundefinedが設定されてしまう return <Button />; } }対応案2: defaultPropsの型を明示的に定義せず型推論させる
defaultPropsの型として明示的に
Props
を指定していましたが、これをやめ型推論を利用してみます。Button.tsxinterface 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.tsxclass 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.tsxinterface 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.tsxinterface 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 />; } }ただし、このままでは
Props
とDefaultProps
の定義が重複しておりメンテナンス性が悪いため、Props
はDefaultProps
を継承するようにします。Button.tsxinterface 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", }; ... }これでばっちりです。
参考
- 投稿日:2019-02-27T10:06:13+09:00
自作アプリにJest+enzyme導入
?初心者です
前回導入編を紹介したので、更にdispatchした時やTextFieldに入力された時のバリデーションテスト等々を、自分用の備忘録がてら紹介していこうと思います。
(…まだまだエラーに躓きっ放しでございます!???)
■ TextFieldに入力した文字が280文字を超えたらエラーを表示させる
今実装しているのが、「280文字を超えたらエラーを表示させる」テキストエリアです。下のように、Material-UIの
TextField
で用意されているerror
やhelperText
のプロパティを使ってエラーを表示させるようにしました。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> ) } }
TextField
にhandleChange
を設置して、stateにあるstatus
に入れています。this.setState()
の引数に280文字以上超えた時のバリデーションを用意しました。ユーザーがリアルタイムで入力してエラーが表示されるようにしています。ちゃんと280文字超えたら
error
がtrue
になってくれるか、テストしていきます。下はテストです。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
を探します。(今回はTextField
にmultiline
を入れてテキストエリアにしています!テキストフォームの場合は.find(input)
です。)
テキストエリアが入力されたイベントはenzymeで用意されているsimulate
を使っています。
最後にTextFieldのerror
プロパティはwrapper.find(TextField).props().error
のように指定してあげます。この記述で正しく動作することを確認できました!▼ エラー解消してくれた記事
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
テスト結果はオールパス!!??
ここに行き着くまでもかなり時間掛かっています…!■ 記事を取得後、記事の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で確認しています!長くなったので、今回はここで締めることにします。次回もテストについて!?
【やってみて】
絶っっ対他に方法がある…と確信しています。
冗長的だし、より複雑なテストをするとなると今のコードでは太刀打ち出来ないかも?
もっと深掘りしていきます。【終わりに】
今携わっている案件で、バグの対応を依頼されました。
エラーを見ると共通ヘッダーコンポーネントでエラーが出ている様子…
ほぼ全コンポーネントでヘッダー読み込ませているので、例えば1箇所変更してもどこがどう変わっちゃうのか分かりません…!
スナップショットテストを導入したら、その話をQiitaに載せようと思います。話は脱線しますが、このQiita記事を書いている間に、VSCodeのテーマを変更しました♫
今のテーマは「Dracula Official」!!!?♂️
ピンクが可愛い!!!!(こちらの記事を参考にVSCodeのプラグインを入れたり消したりしました。The Ultimate VSCode Setup for Front End/JS/React)
エラー画面を見ても盛り下がらない工夫必要w
- 投稿日:2019-02-27T00:21:37+09:00
自主勉強継続するために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切ってやってください。
私はReactを使いたかったので、
create-react-appコマンドで環境構築した時点でpushしてしまいました。
やりたいことをissueに書き出す
はい。面倒臭がりな私ですが、無駄に凝り性なので、やりたいことをissueに書きます。私の粒度は下記のような感じでした
書いてみると、割とやること多いんですよね。そこでアジャイルの思想が出てくるのですが、基本的に完成版をイメージしてはいけません。あくまでも直近でやるべきことから書いていきます。私の場合は環境構築で挫折することが多い人間なので、環境構築ステップを一通り洗った時点で一度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のサンプルをコピっただけ)
issue単位でpush&pullrequest
動く単位でcommitを繰り返したら、リモートにpushしましょう
git push origin developそしたら、pullrequestを作成しましょう。(GitHubのbranchの横にあります。)
developからmasterに向けてのpullrequestを作成します。(私はpackage.lock.jsonでconflict起きちゃいましたが)
ここで、pullrequestに関連するissueを本文に追加しておくとタスク管理がかなり楽になります。
pullrequestを作成したら、スカッシュマージしてしまいましょう。(赤いのはレビュー通らないとマージできない設定入れているからです。通常は緑)
スカッシュマージは、▼から選べます。基本的にスカッシュを推奨します。スカッシュマージは、pullrequestでマージする全てのcommitをpullrequestのタイトルcommit1つにまとめてくれるため、historyがすっきりします。また、コミット内容が確認したければ、githubのinsight > networkから、commitを辿ることで細かい修正も確認できます。
またpullrequestにissueの番号を入れておくことで、issueの方にも関連が持てます。(pullrequestで#3を説明部分に記載した時)
明示的にissueが完了したことがわかるので、issueはcloseしちゃいましょう
あとは、developにブランチを切るところからの繰り返し
まとめ
タスク管理ツールなんていくらでもあるので適材適所だとは思いますが、今回はgithubだけで管理してみました。長い目で機能を考えると大抵挫折するので、アジャイルに細々としたリリースを心がけ、なおかつ直近にできるタスクをissue化して実装を繰り返していった方が、個人での実装なら良いのでは無いかと。
まあ、本来のissueの使い方では無いと思いますが、私はこんな使い方をしてみましたと言う共有でした。もっと便利に運用できるぜってコメントがあればぜひご教授ください。(本来の使い方じゃ無いぜ。とか、よくみたら変なcommit入っているやんけとかの意見もあると思いますが、勉強中の身ゆえ許してください)
# 補足
こんなふわっとしたやり方じゃダメでしょって人は、もっと丁寧に書いてくれた人がいました
https://qiita.com/suzuki-hoge/items/3a568dff36fd981082ba
ただ、個人の勉強レベルで期限決めたりテンプレ作ったりはその時点でめんどくさくなっちゃうので、これくらいなら一人でも継続できそうだなってレベルで今回の記事は書きました。