- 投稿日:2019-08-19T19:46:41+09:00
一番簡単なreact-reduxアプリケーション
初めに
モダンなフロントエンド開発だと結構必須になってきているreact-redux
しかしながら、reactだけでも慣れが必要なのにそれに加えてreduxとなると余計混乱します。
僕はめっちゃ混乱しました。チュートリアルをやってみてもなかなか理解が深まらず、使えぬまま放置してしまったことがあります。そういう人も多からずいらっしゃると思うのでそんな方や、reduxよくわかんないけどともかく慣れたいという人のために、今回はものすごく簡単なアプリケーションを作成することによって、もっとも簡単なreduxに触れてみたいと思います。reduxのハードルが少しでも下がれば幸いです。
これぐらいの大きさのアプリケーションだと正直reduxを実装する意味は皆無ですが練習です!
※図を使った細かなデータの行き来は他の方が詳しく解説されているので、今回はそういったものは用意しておりませんのでご了承ください
作成目標
+を押したら数字が増えて
-を押したら数字が減る
そんな簡単なアプリケーション
herokuでdeployしてます
https://easiest-react-redux-app.herokuapp.com/
作成したコードはgitにあげております(デプロイに際してコードが少し変化してます)
https://github.com/keyyang0723/easiest-react-redux環境構築
reactをやるうえで最も簡単な
create-react-appを使います$npm install -g create-react-app $create-react-app easiest-react-reduxこれでアプリケーションが作成されます。
ここからはコンソールコマンドは基本的にアプリのホームディレクトリで行っていると思ってください$npm run start
でReactのマークが回転していればOK
reduxの導入
$npm install react-redux -s $npm install redux -s不要ファイルの削除
次に不要なファイルを削除します
srcの中身はIndex.js,App.js以外は不要なので任意の方法で削除してください。必要なフォルダの作成
必要なフォルダを作成します。
それぞれreduxで使用する
actions
reducers
components
containers
をsrc直下に作成します。
この時ついでにApp.jsをcomponents直下に移動させておきます。$mkdir src/actions $mkdir src/reducers $mkdir src/components $mkdir src/containers $mv src/App.js src/components/App.jsこれで作成の準備ができました。お疲れ様です。
実装
Viewの作成
まずはひとまずreduxを無視してreactを使いviewを作成しましょう
index.jsimport React from "react"; import { render } from "react-dom"; import App from './components/App'; const rootElement = document.getElementById("root"); render(<App />, rootElement);App.jsimport React,{ Component } from "react"; class App extends Component{ render(){ return ( <div> <p>0</p> <button>+</button> <button>-</button> </div> );} } export default Appなんの変哲もないreactのコンポーネントです。
$npm run start
すれば表示はされますが、もちろん値の更新などは行われません。
従来のreactであればここにApp.jsのstateを使い,buttonにイベントを用意してそれらを着火させstateを更新していきます。(このアプリケーションはそれだけの機能なので従来ならばそのやり方でOKですが今回は特別です)
ではここからはreduxを実際に利用していきましょう。reduxの導入
storeの作成を実装する
まずはデータの置き場であるstoreを初期化する部分を作成していきましょう。
storeを通じて画面を表示させるためにはactions=>reducersのデータの流れが作成されていないとエラーが出てしまうので、エラーが出ない程度に実装します。actionsの作成
まずはactionを作成します。actionですが今回のアプリケーションは数字の増加と減少二つの機能があるので両方追加しておきます。
ここではaction typeの指定とdispatchされてきた初期値の受け渡しのみ実装しておきます(typeを指定しないと怒られる)actions/index.js/*actionの作成*/ export const INCREMENT = "INCREMENT"; export const DECREMENT = "DECREMENT"; export const increment = (number) => { return { type: INCREMENT, number }; }; export const decrement = () => { return { }; };またtypeは文字列のまま使うこともできますが、constで定義しておくのがイカした書き方のようです。
reducersの作成
次にreducerの作成です。
従来ならばactionsから送られてきたtypeによって分岐分けし、データ作成の機能を作成しますが、今回は表示だけなので受け取った値をそのまま渡します。reducers/index.js/*reducerの作成*/ const reducer = (state, action) => { switch (action.type) { default: return state; } }; export default reducercreateStoreでstoreの初期化の実装
storeを作成するためにreduxのcreateStore()を使用します。こちらはreducerを引数に渡す必要があるので、import作成します。
index.jsimport React from "react"; import { render } from "react-dom"; import { Provider } from "react-redux"; import { createStore } from "redux"; import App from './components/App'; import reducer from './reducers/index'; const store = createStore(reducer); const rootElement = document.getElementById("root"); render( <Provider store={store}> <App /> </Provider> , rootElement);またこの際初期値の挿入のため普段はここには配置しないactionsを配置しています。後で消します
react-reduxのProviderを利用を利用しここにラップされているコンポーネントすべてからstoreの値を参照できるようにします。
これで問題がなければ
$npm run start
をたたけば先ほど作成したviewと同じ画面が表示されます。
これでstoreを作成することに成功しました!
ここから、actionとreducerを実装していき、console.logを使い正しいデータの流れができているか確認します。actionの実装
actionはdispatchから送られてくるstateを受け取り、そこから値を抽出してreducerに送ります。ここでは値に触りません。
actions/index.js/*actionの作成*/ export const INCREMENT = "INCREMENT"; export const DECREMENT = "DECREMENT"; export const increment = state => { return { type: INCREMENT, number: state.number }; }; export const decrement = state => { return { type: DECREMENT, number: state.number }; };rudexの書式に沿っていればこれだけ書くだけで自動的に値をreducerに送ってくれます。
reducerの実装
次にreducerを実装してみます。
reducer/indexl.js/*reducerの作成*/ const reducer = (state, action) => { console.log(action)//あとでデータの確認に使います。本来ならば不要です。 switch (action.type) { case "INCREMENT": return { number: action.number + 1 }; case "DECREMENT": return { number: action.number - 1 }; default: return state; } }; export default reducer;reducerはactionsから流れてきたstate, actionを受けとり値を変更しstoreを更新します。
今回では+を押したときにINCREMENTで値を+1し、-を押したときにDECREMENTで値を-1します。これでいったんactionsとreducersの作成が終わりました。この二つによってstoreがうまく変更されるかを確認してみましょう。
index.jsimport React from "react"; import { render } from "react-dom"; import { Provider } from "react-redux"; import { createStore } from "redux"; import App from './components/App'; import reducer from './reducers/index'; import { increment, decrement } from './actions/index'; const store = createStore(reducer); store.dispatch(increment({number: 0}))//テスト用コードです store.dispatch(decrement({number: 0}))//テスト用コードです const rootElement = document.getElementById("root"); render( <Provider store={store}> <App /> </Provider> , rootElement);今回はデータの確認だけですのでindex.jsに直接actionsをimportし先ほどreducersに用意していたconsole.logにデータが正しく表示されているかを確認しましょう。
store.dispatch(increment({number: 0})); //store.dispatch(decrement({number: 0}));のようにコメントアウトを切り替えることで値がそれぞれ
{type: "INCREMENT", number: 0}
{type: "DECREMENT", number: 0}
のふたつに切り替わっていれば無事実装されています。確認ができたらreducers/index.jsとindex.jsにあるテストようのコードを削除しておきましょう。
おめでとうございます!これであなたはstore, actions, reducersを用いてデータの流れを作成することに成功しました!あとはこれらを着火するイベントと変更されてた値を表示する機能を作成すれば完成です!
componetsの作成
さて、いよいよ今まで作成したstore, actions, reducersを用いて画面を作成していきましょう。
storeで値を保持してそれらを表示させ、イベントなどでactionに値とaction typeを流し込みreducerでstoreを更新するというサイクルを作成します。mapStateToPropsとmapDispatchToPropsに関して
ここで新たにmapStateToPropsとmapDispatchToPropsというreduxで用いられる関数が登場します。
個人的にここが結構ややこしいと思うので
今回では
mapStateToPropsはstoreの値を監視しそれらを適切な形に変化させて、表示させるもの
mapDispatchToPropsはstoreの値を変化させるためのイベントを宣言するもの
くらいに考えておいてもらってよいと思います。
特に後者はdispatch(increment({ number: number }));などという見慣れないものが現れますがdispatch()のカッコ内に記載したactionsの関数に値を送りこむものくらいで結構です。componets部分の変更
まずはApp.jsを少し書き換えます、今回はClassである必要はないので
const App = () =>( <React.Fragment> <p>0</p> <button>+</button> <button>-</button> </React.Fragment> )のように書き換えます
mapStateToPropsを使ってstoreの値を表示する
そしてこのAppにstoreからの値を流し込むためにmapStateToPropsを記入し、react-reduxのconnectを用いてAppをとつないでやります。
App.jsimport React from "react"; import { connect } from "react-redux"; const App = (state) =>( <React.Fragment> {console.log(state)}//テスト用です。 <p>{state.number}</p> <button>+</button> <button>-</button> </React.Fragment> ) function mapStateToProps(state) { return { number: state ? state.number : 0 }; } export default connect( mapStateToProps, null )(App);このような感じです。今回の場合、初期表示時のためstoreから流れてくるstateはnullですが、
number: state ? state.number : 0によって0に変えています
コンソールに{number: 0, dispatch: ƒ}が来ていれば問題なくいっています。
さぁ、これでstoreの値をviewで表示することができました!!
あとはstoreの値を変更するイベントをmapDispatchToPropsを用いて実装すれば完成です!mapDispatchToPropsを実装する
mapDispatchToPropsではstateの状態を変更するイベントを作成しAppに渡してやります。そしてApp内でそれを着火することでstateの変更を行います。
まずは+のボタンのイベントから実装していきます。function mapDispatchToProps(dispatch){ return { onClick(number) { dispatch(increment({ number: number })); } } }関数として表示されている数字のnumberを受け取りincrementにdispatchしてやるだけです。
components/App.jsimport React from "react"; import { connect } from "react-redux"; import { increment } from '../actions/index'; const App = (state) =>( <React.Fragment> {console.log(state)} <p>{state.number}</p> <button>+</button> <button>-</button> </React.Fragment> ) function mapStateToProps(state) { return { number: state ? state.number : 0 }; } function mapDispatchToProps(dispatch){ return { onClick(number) { dispatch(increment({ number: number })); } } } export default connect( mapStateToProps, mapDispatchToProps )(App);最後にconnectでAppに忘れずにつないでやります。
これでconsole.logを見てやると、stateの中のdispatchに記載したonClickが表記されています。後はdecrement部分も実装しのonClickにわたしてやればOKです。<button onClick={() => {state.onClick(state.number,INCREMENT)}}>+</button>これで+ボタンの実装が完了しました。同様に―ボタンを作成すれば機能の実装は完了です!!
おめでとうございます!!これでかなり小さいものですがreact-reduxアプリケーションが完成しました!!
最後に今回は小さいアプリケーションなので分けずに書いてしまっている部分を分断していきます。containersの作成
components/App.jsに直接書いてしまっているactionへの値のやり取りやstoreからの値の取り込みを行う部分をcontainers/dispayNumber.jsとして作成していきます。また今回は一機能なので必要ないように感じますが
機能が増えたときにcomponents/App.jsでまとめてからindex.jsに渡したいので、components/displayNumber.jsを作成しcomponents/App.jsでimportしてやりますcomponents/App.jsimport React from "react"; import DisplayNumber from "../containers/displayNumber"; export default function App(){ return( <React.Fragment> <DisplayNumber /> </React.Fragment> ) }components/Counter.jsimport React from 'react'; import { INCREMENT, DECREMENT } from "../actions/index"; const Counter = (state) =>( <React.Fragment> <p>{state.number}</p> <button onClick={() => {state.onClick(state.number,INCREMENT)}}>+</button> <button onClick={() => {state.onClick(state.number,DECREMENT)}}>-</button> </React.Fragment> ) export default Countercontainers/dispayNumber.jsimport { connect } from "react-redux"; import Counter from "../components/Counter"; import { INCREMENT, increment, DECREMENT, decrement } from "../actions/index"; function mapStateToProps(state) { return { number: state ? state.number : 0 }; } function mapDispatchToProps(dispatch) { return { onClick(number, types) { switch (types) { case INCREMENT: dispatch(increment({ number: number })); break; case DECREMENT: dispatch(decrement({ number: number })); break; default: } } }; } export default connect( mapStateToProps, mapDispatchToProps )(Counter);お疲れ様です!これで形も整えることができ無事react-reduxアプリケーションが完成しました!!
今回は非常に機能の少ないアプリケーションであり、まだまだ使用していない機能もたくさんありますが、redux理解への第一歩としてお役に立っていれば幸いです。
初期値の追加
この状態だと初期stateが導入されておらず、初期表示を行いたいときに若干不都合でした
index.jsimport React from "react"; import { render } from "react-dom"; import { Provider } from "react-redux"; import { createStore } from "redux"; import App from './components/App'; import reducer from './reducers/index'; const store = createStore(reducer, initialState); const rootElement = document.getElementById("root"); render( <Provider store={store}> <App /> </Provider> , rootElement); const initialState = { number: 0 };const store = createStore(reducer,initialState);const initialState = { number: 0 };を加え初期stateを追加してやりましょう。
参照
デプロイの時お世話になりました。
https://qiita.com/kuniken/items/70c2b5cd77d7c9301bcf#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%BD%E3%83%BC%E3%82%B9
- 投稿日:2019-08-19T19:46:40+09:00
npx create-react-app my-app --typescriptのあと
npx create-react-app my-app --typescript # りんと npm install --save tslint # スタイル npm install --save styled-components @types/styled-components # air-bnbのりんとやりたい npm install --save-dev tslint-config-airbnbtslint.json{ "extends": "tslint-config-airbnb", }airbnbのりんとのめんどいやつはfalseにしたいので
tslint.json{ "extends": "tslint-config-airbnb", // 追加 "rules": { "import-name": false, "variable-name": false, } }
- 投稿日:2019-08-19T16:43:12+09:00
react-hooksでreduxを使う
react-hooksでreduxを使う
案件でreactの改修が起きそうなので、react,redux,typescript,react-routerで気持ちよく書きたいと思い殴り書きだがサンプルを作って試してみました。
作ったリポジトリ
https://github.com/numa999/react-redux-example
ざっくり解説
router.tsximport React from 'react' import { BrowserRouter, Route, Link } from 'react-router-dom' import App from './App' import Counter from './counter' const Router: React.FC = () => ( <BrowserRouter> <nav> <ul> <li><Link to="/">HOME</Link></li> <li><Link to="/counter">COUNTER</Link></li> </ul> </nav> <hr/> <div className="content"> <Route exact path="/" component={ App } /> <Route path="/counter" component={ Counter } /> </div> </BrowserRouter> ) export default Routerここは特に工夫していないです。ナビゲーションも適当。
counter.tsximport React, { useState } from 'react' import { useSelector, useDispatch } from 'react-redux' import { RootState, incrementActionCreator, decrementActionCreator } from './store'; const Counter: React.FC = () => { const [count, setCount] = useState(0) const dispatch = useDispatch() const counterState = useSelector((state: RootState) => state.counter) return ( <div> react hooks count { count } <div> <button onClick={() => setCount(count + 1)}>+</button> <button onClick={() => setCount(count - 1)}>-</button> </div> redux count { counterState.count } <div> <button onClick={() => dispatch(incrementActionCreator())}>+</button> <button onClick={() => dispatch(decrementActionCreator())}>-</button> </div> </div> ) } export default Counterreact-hooksもそんな使ってないのでちょっと余計なコードも追加。
useSelector
前までの
mapStateToProps
の代わり
https://react-redux.js.org/next/api/hooks#useselectoruseDispatch
storeのdispatchを生成してくれる。アクションの中身を指定して渡してあげる。useCallbackは一旦省略。
https://react-redux.js.org/next/api/hooks#usedispatchstore
store.tsimport { combineReducers, createStore } from 'redux' // Counter State interface CounterState { count: number } const counterInitialState: CounterState = { count: 0 } // actions export const INCREMENT = 'COUNTER/INCREMENT' export const DECREMENT = 'COUNTER/DECREMENT' interface IncrementAction { type: typeof INCREMENT payload: null } interface DecrementAction { type: typeof DECREMENT payload: null } type CounterActionTypes = IncrementAction | DecrementAction export const incrementActionCreator = (): CounterActionTypes => { return { type: INCREMENT, payload: null } } export const decrementActionCreator = (): CounterActionTypes => { return { type: DECREMENT, payload: null } } //--- // reducers export const counterReducer = ( state = counterInitialState, action: CounterActionTypes ): CounterState => { switch (action.type) { case INCREMENT: return { count: state.count + 1 } case DECREMENT: return { count: state.count - 1 } default: return state } } //--- // combine export const rootReducer = combineReducers({ counter: counterReducer }) export type RootState = ReturnType<typeof rootReducer> export const store = createStore(rootReducer)
ReturnType
知らなかった。
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#example-4感想
ここにreset用のアクションとか付け足したいけど省略。最近vuexばっかり使っていたので、比較でもないですが僕はやっぱりreactの方が好きだなと思った。
参考
https://react-redux.js.org/next/api/hooks
https://redux.js.org/recipes/usage-with-typescript
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html
https://qiita.com/seya/items/8291f53576097fc1c52a
- 投稿日:2019-08-19T12:54:30+09:00
Material UI の Shadow を完全にオフる方法(React)
はじめに
Button をオーバーライドして
shadow = 'none'にしておけば、オフにできるかなと思ったけど
押下した時に現れる影は消せていないことが判明したので、ちょこっと付け加えることにした押下前
押下時
解決
これだけ
none の付け方に注目import {createMuiTheme, makeStyles} from '@material-ui/core/styles'; import {MuiThemeProvider} from "@material-ui/core"; const theme = createMuiTheme({ shadows: ["none"] }); const Index = () => { return( <MuiThemeProvider theme={theme}> <Button variant={valiant} className={classes.button}> ボタンだよん </Button> </MuiThemeProvider> ) }最後に
「この画面では、俺はリプルエフェクトだけが欲しいんだ!!!影はいらん!!」
という方は試してみてくださいな
- 投稿日:2019-08-19T01:09:51+09:00
React + material-UIでwebアプリ開発練習
あくまで個人のメモです。
Reactとmaterial-UIを使ったwebアプリ練習
Live Chat App with React Tutorial | React Hooks, Material UI, Socket.io, Node
を参考にしています(YouTube)。
というか、ここに書かれたコードをこの動画で説明されています。準備
$ npx create-react-app mui-test $ npm install material-ui $ npm install socket $ npm install expressコード
mui-test/public/index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> <meta name="description" content="Web site created using create-react-app" /> <link rel="apple-touch-icon" href="logo192.png" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <title>React App with Material-UI</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> </body> </html>mui-test/src/index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; ReactDOM.render(<App />, document.getElementById('root'));src/index.cssbody { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; }src/App.jsimport React from 'react'; import './App.css'; import Dashboard from './Dashboard'; import Store from './Store'; class App extends React.Component { render() { return ( <div className="App"> <Store> <Dashboard /> </Store> </div> ); } } export default App;src/App.css.App { text-align: center; }src/Dashboard.jsimport React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Paper from '@material-ui/core/Paper'; import Typography from '@material-ui/core/Typography'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import ListItemText from '@material-ui/core/ListItemText'; import Chip from '@material-ui/core/Chip'; //import Avatar from '@material-ui/core/Avatar'; import Button from '@material-ui/core/Button'; import TextField from '@material-ui/core/TextField'; import { CTX } from './Store'; const useStyles = makeStyles(theme => ({ root: { margin: '50px', padding: theme.spacing(3, 2), }, flex: { display: 'flex', alignItems: 'center', }, topicsWindow: { width: '30%', height: '300px', borderRight: '1px solid grey', }, chatWindow: { width: '70%', height: '300px', padding: '20px', }, topicBox: { width: '30%', padding: '10px', }, topicButton: { width: '15%', }, userBox: { width: '20%', padding: '10px', }, chatBox: { width: '65%', padding: '10px', }, button: { width: '15%', }, })); function Dashboard() { const classes = useStyles(); // CTX store const {allChats, sendChatAction, addTopicAction} = React.useContext(CTX); const topics = Object.keys(allChats); // local state const [activeTopic, changeActiveTopic] = React.useState(topics[0]); const [textValue, changeTextValue] = React.useState(''); const [userValue, changeUserValue] = React.useState(''); const [addTopicValue, changeAddTopicValue] = React.useState(''); return ( <div> <Paper className={classes.root}> <Typography variant="h4" component="h4"> メッセージアプリ </Typography> <Typography variant="h5" component="h5"> {activeTopic} </Typography> <div className={classes.flex}> <div className={classes.topicsWindow}> <List> { topics.map(topic => ( <ListItem onClick={e => changeActiveTopic(e.target.innerText)} key={topic} button > <ListItemText primary={topic} /> </ListItem> )) } </List> </div> <div className={classes.chatWindow}> { allChats[activeTopic].map((chat, i) => ( <div className={classes.flex} key={i}> <Chip label={chat.from} className={classes.chip} /> <Typography variant="body1" gutterBottom > {chat.msg} </Typography> </div> )) } </div> </div> <div className={classes.flex}> <TextField label="add topic" className={classes.topicBox} value={addTopicValue} onChange={e => changeAddTopicValue(e.target.value)} /> <Button variant="contained" color="primary" className={classes.topicButton} onClick={() => { addTopicAction(addTopicValue); changeAddTopicValue(''); }} > add </Button> </div> <div className={classes.flex}> <TextField label="user name" className={classes.userBox} value={userValue} onChange={e => changeUserValue(e.target.value)} /> <TextField label="Send a chat" className={classes.chatBox} value={textValue} onChange={e => changeTextValue(e.target.value)} /> <Button variant="contained" color="primary" className={classes.button} onClick={() => { sendChatAction({ from: userValue, msg: textValue, topic: activeTopic }); changeTextValue(''); changeUserValue(''); }} > Send </Button> </div> </Paper> </div> ); } export default Dashboard;src/Store.jsimport React from 'react'; import io from 'socket.io-client'; export const CTX = React.createContext(); /* msg: { from: 'user', msg: 'message', topic: 'general' }, state: { topic1: [msg, msg, msg, ...], topic2: [msg, msg, ...], } */ const initState = { general: [ ], } function reducer(state, action) { const {from, msg, topic} = action.payload; switch(action.type) { case 'RECEIVE_MESSAGE': return { ...state, [topic]: [ ...state[topic], {from, msg, } ] } case 'ADD_TOPIC': return { ...state, [topic]: [], } default: return state; } } let socket; function sendChatAction(value) { socket.emit('chat message', value); } function addTopicAction(value) { socket.emit('add topic', value); } function Store(props) { const [allChats, dispatch] = React.useReducer(reducer, initState); if(!socket) { socket = io(':3001'); socket.on('chat message', msg => { dispatch({type: 'RECEIVE_MESSAGE', payload: msg}); }); socket.on('add topic', topic => { if (!allChats[topic]) { dispatch({type: 'ADD_TOPIC', payload: {from:'', msg:'', topic}}); } }) } return ( <CTX.Provider value={{allChats, sendChatAction, addTopicAction}}> {props.children} </CTX.Provider> ); } export default Store;サーバー
src/server/index.jsvar app = require('express')(); var http = require('http').createServer(app); var io = require('socket.io')(http); app.get('/', (req, res) => { res.send('<h1>Hello</h1>'); }); io.on('connection', socket => { console.log('a user connected'); socket.on('chat message', msg => { console.log('message: ' + JSON.stringify(msg)); io.emit('chat message', msg); }); socket.on('add topic', topic => { console.log('topic: ' + topic); io.emit('add topic', topic); }) }); http.listen(3001, () => { console.log('listening on *:3001'); });起動
$ node index.jsまとめ
material-ui使うときれいな見た目になる。
- 投稿日:2019-08-19T00:08:08+09:00
Storybook + React.jsでViewにロジックや静的ファイルを導入する
背景
- Storybook とはコンポーネント毎にViewを可視化するツールです。
- i18nなどでテキストを管理していたため動的にファイルを変更させたかったのですが、公式ドキュメントなどを記述がありませんでしたので補足です。
下記のように記述すると、エラーで怒られます。
こんな風に使いたかったimport * as React from 'react' import { useTranslation } from 'react-i18next' // react-i18next を呼ぶ時は別途スクリプトの記述が必要ですが略 // 公式を参照するとわかります。 // https://github.com/i18next/react-i18next storiesOf("HelloWorld", module) .add("HelloWorld", () => { const { t } = useTranslation() return ( <HelloWorld>t('hello_world')</HelloWorld> ) })エラーメッセージInvariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: 1. You might have mismatching versions of React and the renderer (such as React DOM) 2. You might be breaking the Rules of Hooks 3. You might have more than one copy of React in the same app See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.解決策
React.createElement
を導入します。これだと動きますimport * as React from 'react' import { useTranslation } from 'react-i18next' storiesOf("HelloWorld", module) .add("HelloWorld", () => React.createElement(() => { const { t } = useTranslation() return ( <HelloWorld>t('hello_world')</HelloWorld> ) }) )静的ファイルの参照方法
webpack
の出力先をstorybookにも適用してあげるだけです。-s
オプションを使用します。下記は
webpack
の出力先がdist
の場合package.json{ // 略 "scripts": { "storybook": "start-storybook -p 9001 -c .storybook -s ./dist", }, // 略 }参考