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
一先ず、ふたつのテキストボックスに入力された数字を足すだけのなにかを作ってみようと思います。
設定
右上のPreferencesからエディタの設定を変更できます。
初期状態だと行末のセミコロンを自動で付加する設定になっているのでOFFにしました。
ショートカット
Emmetの展開 → Ctrl+E
選択中文字列と同じものを選択 → Ctrl+D
このあたりが使えるのが個人的にはかなりうれしいです。
実装
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にあります。
恥ずかしながらしばらく探したので念の為。
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の方の指定も忘れずに。
ちゃんと動きました。ぱちぱち。
折角だしテストも書きたい
- ボタンをクリックしたらsumNum()が呼ばれること
- sumNum()が正常に動作していること
以上をテストしたいと思います。
パッケージは、左メニューから「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
import React from 'react'
import ReactDOM from 'react-dom'
import './style.css'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))
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
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
(;゜Д゜) 気付いたら表示と処理が違うファイルにある……。
これで合ってるのか? 本当にあってるのか? と胸に疑念を抱きつつとりあえずテストを書いてみます。
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)
})
})
↓結果
ちゃんと動きました。
最初は表示と処理が違うコンポーネントにあるのが気持ち悪かったんですが、
テストを書いてみると、ここが別々になっていた方が分かりやすいのかなと少し思いました。
このあたりはフワッとしか理解出来ていないのでまたの機会に。
できあがったものがこちら
CodeSandbox自体はサクサク書けて、かなり楽しいです。