LoginSignup
16
19

More than 5 years have passed since last update.

CodeSandboxでReactに触れてみる

Posted at

Vueは書いたことがあるけれど、Reactは未着手な人間が、
今回はCodeSandboxで手軽にReactに触れてみました。

CodeSandbox

https://codesandbox.io
オンラインエディタ。
React以外にもVueやAngularも使用できます。

とりあえず何か書いてみる

参考:
React.jsで社内システムを作る(Reactコンポーネント編)
https://doda.jp/d-c-b/article/180213.html
React.js コンポーネント入門(props/state)
https://qiita.com/KeitaMoromizato/items/0da6c8e4264b1f206451
React.jsのState
https://qiita.com/koba04/items/63267bcc918d76ac8767

一先ず、ふたつのテキストボックスに入力された数字を足すだけのなにかを作ってみようと思います。

設定

image.png
右上のPreferencesからエディタの設定を変更できます。

image.png
初期状態だと行末のセミコロンを自動で付加する設定になっているのでOFFにしました。

ショートカット

Emmetの展開 → Ctrl+E
選択中文字列と同じものを選択 → Ctrl+D

このあたりが使えるのが個人的にはかなりうれしいです。

実装

src/index.js
import React from "react"
import ReactDOM from "react-dom"
import "./style.css"
import App from "./App"

ReactDOM.render(<App />, document.getElementById("root"))

ReactDOM.renderでAppコンポーネントを<div id="root">に紐づけているのだとざっくり理解。

<div id="root">を持つindex.htmlはpublicにあります。
 恥ずかしながらしばらく探したので念の為。

src/App.js
import React from "react"

class App extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      currentTxt: 0,
      num1: 0,
      num2: 0
    }
  }

  sumNum() {
    this.setState({
      currentTxt: parseInt(this.state.num1, 10) + parseInt(this.state.num2, 10)
    })
  }

  render() {
    return (
      <div className="container">
        <div className="sumText">{this.state.currentTxt}</div>
        <input
          type="number"
          className="input-text"
          value={this.state.num1}
          onChange={el => this.setState({ num1: el.target.value })}
        />
        <span className="arith-symbol">+</span>
        <input
          type="number"
          className="input-text"
          value={this.state.num2}
          onChange={el => this.setState({ num2: el.target.value })}
        />
        <input
          type="button"
          className="input-btn"
          onClick={() => this.sumNum()}
          value="計算"
        />
      </div>
    )
  }
}

export default App

this.stateはVueで言うところのdataだと理解。
あとはVueのmethodsよろしく処理を書いて、onClickやonChangeで呼び出せばとりあえず動くようです。inputはvalueの方の指定も忘れずに。

image.png

ちゃんと動きました。ぱちぱち。

折角だしテストも書きたい

  • ボタンをクリックしたらsumNum()が呼ばれること
  • sumNum()が正常に動作していること

以上をテストしたいと思います。

image.png
パッケージは、左メニューから「File Editer」をクリック、Dependenciesを開いたところの「Add Dependency」から追加できます。
とりあえずJestとenzyme、enzyme-adapter-react-16を追加します。

参考:
Facebook製のJavaScriptテストツール「Jest」の逆引き使用例
https://qiita.com/chimame/items/e97883fd46b67529d59f
Jest + enzymeで行うReactのUT(ユニットテスト )について
https://mae.chab.in/archives/60066

そしてしばらく格闘した末に、どうもここまで書いたコードのままではテストがうまく行かないようなので、まず元のコードを修正します。
ついでに、もしかして既にサンプルとはいえApp.jsに直で書いてはいけない段階に来ているのではないだろうか? という結論に達したのでコンポーネントを分離します。
そうなるとinput[type="number"]の関数もちゃんと分離しないとダメですね、ハイ。

参考:
React.jsのPropTypesの一覧
http://morizyun.github.io/javascript/react-js-proptypes-validator.html
[react]子のコンポーネントから親のstateを変更する方法
https://qiita.com/w-tdon/items/7b0f72a3b0a3e0708741

index.js
import React from 'react'
import ReactDOM from 'react-dom'
import './style.css'
import App from './App'

ReactDOM.render(<App />, document.getElementById('root'))

App.js
import React from 'react'
import SumBlock from './SumBlock'

class App extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      currentText: 0,
      num1: 0,
      num2: 0
    }

    this.changeInputNum = this.changeInputNum.bind(this)
    this.sumNum = this.sumNum.bind(this)
  }

  changeInputNum(target, num) {
    this.setState({
      [target]: num
    })
  }
  sumNum() {
    this.setState({
      currentText: parseInt(this.state.num1, 10) + parseInt(this.state.num2, 10)
    })
  }

  render() {
    return (
      <div className="container">
        <SumBlock
          onChangeNum={this.changeInputNum}
          onClickBtn={this.sumNum}
          {...this.state}
        />
      </div>
    )
  }
}

export default App

SumNum.js
import React from 'react'
import PropTypes from 'prop-types'

const propTypes = {
  onChangeNum: PropTypes.func
}

class SumBlock extends React.Component {
  render() {
    const { onChangeNum } = this.props
    const { onClickBtn } = this.props
    return (
      <div className="sumBlock">
        <div className="sum-text">{this.props.currentText}</div>
        <input
          type="number"
          className="input-text num1"
          value={this.props.num1}
          onChange={el => onChangeNum('num1', el.target.value)}
        />
        <span className="arith-symbol">+</span>
        <input
          type="number"
          className="input-text num2"
          value={this.props.num2}
          onChange={el => onChangeNum('num2', el.target.value)}
        />
        <input
          type="button"
          className="input-btn submit-btn"
          onClick={onClickBtn}
          value="計算"
        />
      </div>
    )
  }
}

SumBlock.propTypes = propTypes
export default SumBlock

(;゜Д゜) 気付いたら表示と処理が違うファイルにある……。
これで合ってるのか? 本当にあってるのか? と胸に疑念を抱きつつとりあえずテストを書いてみます。

App.test.js
import React from 'react'
import Enzyme from 'enzyme'
import { shallow } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import App from '../src/App'
import SumBlock from '../src/SumBlock'

Enzyme.configure({ adapter: new Adapter() })

describe('SumBlock', () => {
  test('ボタン動作チェック', () => {
    let sumNum = jest.fn()
    const subject = shallow(<SumBlock onClickBtn={sumNum} />)
    subject.find('.submit-btn').simulate('click')
    expect(sumNum).toBeCalled()
  })

  test('sumNum計算チェック', () => {
    const subject = shallow(<App />)
    subject.setState({ num1: 3 })
    subject.setState({ num2: 2 })
    subject.instance().sumNum()
    expect(subject.state('currentText')).toBe(5)
  })
})

↓結果

image.png

ちゃんと動きました。
最初は表示と処理が違うコンポーネントにあるのが気持ち悪かったんですが、
テストを書いてみると、ここが別々になっていた方が分かりやすいのかなと少し思いました。
このあたりはフワッとしか理解出来ていないのでまたの機会に。

できあがったものがこちら

CodeSandbox自体はサクサク書けて、かなり楽しいです。

16
19
0

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
16
19