since 2023-01-25
create-react-appでは脆弱性の警告が出るが無視して良い
https://zenn.dev/appare45/articles/7f667031aab94b
なるほど
npm audit --production
since 2018-08-16
nodejs に書いた手順で WSL Ubuntu に環境構築してからのメモ。
TODO: 過去の仕事で使った angularjs との比較、移行に関する個人的な考察。
$ npx create-react-app my-app
完了すると my-app フォルダができている。
$ ls my-app/ node_modules package.json package-lock.json public README.md src $ du -s my-app 157866 my-app
ちゃんと .gitignore があるので、ここで git init して initial commit しておく。
$ cd my-app/ $ git init $ git add . $ git commit -am "initial"
開発サーバーの動作を確認する
$ npm start
WSL で作業していたら、ブラウザが開けませんエラーではなく、ちゃんと Windows の Chrome が localhost:3000 を開いてくれる。
この作業フォルダは Windows から編集できる場所に作っておいて、Visual Studio Code のターミナルから WSL の bash をひらいて npm start しておく。
npm start をやり直すたびに localhost:3000 タブが増えてしまうので、基本は動かしっぱなしがよい。
ちなみに npm run build すると静的ファイルを build フォルダに作る。
コンポーネントの概念とファイル分割の話を切り離して考えたい。
そこで App.js のインポートをやめて index.js だけでできることを模索してみる。
Visual Studio Code で my-app フォルダを開いて src/index.js を編集する。
エディタで編集を保存するたびにコンパイルされ、ブラウザがリロードされる。
import React, { Component, Fragment } from 'react'; import ReactDOM from 'react-dom'; class App extends Component { render() { return ( <Fragment> <h1>hello</h1> <div className="text">hello</div> </Fragment> ); } } ReactDOM.render(<App/>, document.getElementById('root'));
とにかく既存の HTML を丸ごと突っ込んでみたい場合は、これが出発点と思われる。
XML を埋め込む記述 (JSX) は Visual Studio Code で快適に扱える。
なお、Chrome 開発者ツールでこのコンテンツを調べていたら、Chrome のアドオンをインストールせよと言われる。
React 開発者ツールを入れておくと React コンポーネントを Chrome で確認できる。
JavaScript で評価される値の埋め込みは {} で行う。
import React, { Component, Fragment } from 'react'; import ReactDOM from 'react-dom'; class App extends Component { render() { const msg = 'world'; return ( <Fragment> <h1>hello</h1> <div className="text">{msg}</div> </Fragment> ); } } ReactDOM.render(<App/>, document.getElementById('root'));
テンプレートエンジンの条件分岐やループ、あるいは ng-if / ng-show / ng-repeat のような処理は、JavaScript で記述。
import React, { Component, Fragment } from 'react'; import ReactDOM from 'react-dom'; class App extends Component { render() { const animalNames = ['dog', 'cat', 'monkey']; const content = animalNames.map((item, index) => { return <li key={index}>{item}</li>; }); return ( <Fragment> <h1>hello</h1> <ul> {content} </ul> </Fragment> ); } } ReactDOM.render(<App/>, document.getElementById('root'));
key 属性はなくても動くが、開発者ツールでつけろという warning が出る。
複雑になるとリファクタリングしようということになる。
ここでようやく新しいクラスを定義。
import React, { Component, Fragment } from 'react'; import ReactDOM from 'react-dom'; class Animals extends Component { render () { const animalNames = ['dog', 'cat', 'monkey']; const content = animalNames.map((item, index) => { return <li key={index}>{item}</li>; }); return ( <ul> {content} </ul> ); } } class App extends Component { render() { return ( <Fragment> <h1>hello</h1> <h2>animals</h2> <Animals/> </Fragment> ); } } ReactDOM.render(<App/>, document.getElementById('root'));
こういう流れで作業をすると、一つの HTML ファイルからちょっとずつコンポーネントに分割する、みたいなリファクタリングが段階的にやれそう。
ところで animalNames は Animals の外にあったほうが便利な場合がある。
一般的には state と props の話が出てくる。
探すとたくさん記事が出てくるので、ここでは省略。
ここではいきなり、状態はグローバルに管理したい、という前提。
AngularJS でいえば $rootScope みたいなことがしたい。
Redux を使えばできる。そこでオレオレ流に突っ込んでみる。
https://github.com/reduxjs/react-redux
準備
$ npm i redux react-redux
新しい index.js
import React, { Component, Fragment } from 'react'; import ReactDOM from 'react-dom'; import { createStore } from 'redux'; import { Provider, connect } from 'react-redux'; const initialState = { animalNames: ['dog', 'cat', 'monkey'], }; const reducer = (state = initialState, action) => { return state; } const store = createStore(reducer); const connectComponent = connect( state => { let o = {}; for (let k in state) { o[k] = state[k]; } return o; }, dispatch => { return { } } ); class Animals extends Component { render () { const content = this.props.animalNames.map((item, index) => { return <li key={index}>{item}</li>; }); return ( <ul> {content} </ul> ); } } Animals = connectComponent(Animals); class App extends Component { render() { return ( <Fragment> <h1>hello</h1> <h2>animals</h2> <Animals/> </Fragment> ); } } App = connectComponent(App); ReactDOM.render( <Provider store={store}><App/></Provider>, document.getElementById('root') );
connect は高階関数で、普通はモジュールの export で実行するようだが、こんな感じで共通化してしかも index.js に突っ込んでみた。
dispatch はプレイスホルダーになっているが、これを使うと、どこでイベントをハンドルしても、どこかのコンポーネントが書き換わる、みたいな処理が作れる。
reverse ボタンを押すと順番がひっくり変わる。
import React, { Component, Fragment } from 'react'; import ReactDOM from 'react-dom'; import { createStore } from 'redux'; import { Provider, connect } from 'react-redux'; const initialState = { animalNames: ['dog', 'cat', 'monkey'], }; const reducer = (state = initialState, action) => { if (action.type === 'reverse') { return { ...state, animalNames: [...state.animalNames].reverse(), }; } return state; } const store = createStore(reducer); const connectComponent = connect( state => { let o = {}; for (let k in state) { o[k] = state[k]; } return o; }, dispatch => { return { action: type => dispatch({type: type}), } } ); class Animals extends Component { render () { const content = this.props.animalNames.map((item, index) => { return <li key={index}>{item}</li>; }); return ( <ul> {content} </ul> ); } } Animals = connectComponent(Animals); class App extends Component { render() { return ( <Fragment> <h1>hello</h1> <h2>animals</h2> <Animals/> <button onClick={() => this.props.action('reverse')}>reverse</button> </Fragment> ); } } App = connectComponent(App); ReactDOM.render( <Provider store={store}><App/></Provider>, document.getElementById('root') );
onClick の値は {} で囲む。ダブルクォートではない。 中身は関数オブジェクトでなくてはいけないので、引数がある場合は無名関数を作る。
animalNames 配列は deep copy しないといけないので […state.animalNames] をつくって reverse() する。
ピリオド3個 … はスプレッド構文(スプレッド演算子)
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax
下記は、ストアの変数名をそのままアクションの名前にしたい場合の実装。
import React, { Component, Fragment } from 'react'; import ReactDOM from 'react-dom'; import { createStore } from 'redux'; import { Provider, connect } from 'react-redux'; const initialState = { passphrase: '', }; const reducer = (state = initialState, action) => { if (action.type in state) { const o = {...state}; o[action.type] = action.value; return o; } return state; } const store = createStore(reducer); const connectComponent = connect( state => { let o = {}; for (let k in state) { o[k] = state[k]; } return o; }, dispatch => { return { action: (name, value) => dispatch({type: name, value: value}), } } ); window.getPassphrase = () => { return store.getState().passphrase; }; window.setPassphrase = (value) => { store.dispatch({type: 'passphrase', value: value}); }; class App extends Component { render() { return ( <Fragment> <h1>passphrase</h1> <div>{this.props.passphrase}</div> </Fragment> ); } } App = connectComponent(App); ReactDOM.render( <Provider store={store}><App/></Provider>, document.getElementById('root') );
Chrome 開発者ツールの JavaScript console で setPassphrase('hoge') などと入力すると、 コンテンツを書き換えることができる。
デバッグとか evaluateJavaScript とかで使える気がする。