- 投稿日:2021-01-18T23:41:32+09:00
React17 Babel7.12.12で、HTML CDN を使ってReactアプリ試作環境を作る
はじめに
タイトルの通りです。
React16 CDN でHelloWorld JSXありなし両方 - Qiita
https://qiita.com/standard-software/items/0cdbcdb2a6d6355c946b上記記事の最新バージョンです。
先の記事では、HTMLファイル単独でした。今度はjsファイルを別にして開発します。
あとで書きますが、HTML ファイルと Jsファイルを別々にするので、Webサーバーを使って開発する必要があります。
ファイル
ファイルです。
<!DOCTYPE html> <html> <head> <script src="https://unpkg.com/react@17/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script src="./Button.js" type="text/babel"></script> <script src="./Application.js" type="text/babel"></script> </head> <body> <div id="root"></div> </body> </html>Application.jsclass Application extends React.Component { constructor(props) { super(props); this.state = { counter: 0, } this.onClick = this.onClick.bind(this); } onClick() { let { counter } = this.state; counter += 1; this.setState({ counter }); } render() { return ( <div> <span>{this.state.counter}</span> <Button onClick={this.onClick} text={this.state.counter} /> </div> ); } } ReactDOM.render(<Application />, document.getElementById('root'));Button.jsclass Button extends React.Component { constructor(props) { super(props); } render() { return ( <button onClick={this.props.onClick} > {this.props.text} </button> ); } }これらを同一フォルダに配置して、Webサーバー経由で起動してください。
すごく簡単ですが次のようなボタンを押すと、テキストとボタンテキストがカウントアップされるページが表示されます。
Webサーバーを起動するには次の記事などご参考ください。
ライブリロード(ファイル更新監視)してくれるWebサーバー browser-sync のはじめの一歩 - Qiita
https://qiita.com/standard-software/items/7a1d237133f0fe7961fdnode.js http-serverコマンドでwebサーバーを起動する - Qiita
https://qiita.com/standard-software/items/1afe7b64c4c644fdd9e4ローカルファイルでブラウザ起動はできない
HTMLをローカルファイルとしてブラウザ起動だと次のようなエラーが出てしまうのでご注意ください。
jsファイルが他のサイトにあるから動かないよ、というエラーです。画面はWindowsです。
Access to XMLHttpRequest at 'file:///C:/.../Button.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https. babel.min.js:1 GET file:///C:/.../Button.js net::ERR_FAILED (anonymous) @ babel.min.js:1 yq @ babel.min.js:1 (anonymous) @ babel.min.js:1 Cq @ babel.min.js:1 kq @ babel.min.js:1 index.html:1 Access to XMLHttpRequest at 'file:///C:/.../Application.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https. babel.min.js:1 GET file:///C:/.../Application.js net::ERR_FAILEDJavaScriptコードがHTMLファイル内にかかれていれば、ローカルファイル起動でも動くのですが、jsファイルがHTMLファイルと別になっていると、ローカルファイル起動では動きません。
以上です。
- 投稿日:2021-01-18T21:36:31+09:00
React/TypeScript: useStateのコールバック関数をpropsで受け取る時のtypeの定義の仕方備忘録
- 投稿日:2021-01-18T21:18:31+09:00
[React] Reactの学習をします(1-3)式の埋め込み
Reactの学習をします(1-3)
Reactの学習をしてみることにしました。
前回の記事 : [React] Reactの学習をします(1-2)コンポーネントの分割教材
likr さんが公開している「Reactチュートリアル1:犬画像ギャラリーを作ろう」という記事を教材に学習させて頂きます。
素晴らしい教材をありがとうございます。
学習日記
本日は「式の埋め込み」~「繰り返し」までやりました!
式の埋め込み
※ 教材から箇条書き的に抜粋させて頂きます。
・JavaScript の式を JSX に埋め込むためには {式}のように書きます。
この {式} の使い方はとても重要ですね。
コンポーネントと props
※ 教材から箇条書き的に抜粋させて頂きます。
・次は親コンポーネントから子コンポーネントへプロパティを渡す方法を扱います。
・呼び出し側の関数である親コンポーネントから、呼び出される側の関数である子コンポーネントへ props を通じてプロパティを渡すことができます。
props は子コンポーネントの関数の引数となります。・JavaScript の式を渡したい場合は{}を使ってください。
・子コンポーネントでは、関数の仮引数の props を通じて渡されたプロパティにアクセスすることができます。
繰り返し
※ 教材から箇条書き的に抜粋させて頂きます。
・これまでは 1 枚の画像のみを表示していましたが、Dog API から複数の画像の URL を取得してそれを一度に表示してみることを考えてみましょう。
・URL の配列を JSX 式の配列に変換すると考えると良いでしょう。
このような処理は配列の map メソッドでできました。・map メソッドで作られる JSX 式は、最も外側の要素に key 属性を付けなければいけません。
App.jsfunction Gallery(props) { const { urls } = props; return ( <div className="columns is-vcentered is-multiline"> {urls.map((url) => { return ( <div key={url} className="column is-3"> <Image src={url} /> </div> ); })} </div> ); }(Galleryコンポーネントの部分だけ抜粋させて頂きました。)
※ 下図のように表示させることができました!ありがとうございます!
- 投稿日:2021-01-18T20:45:07+09:00
React-Calendarで超簡単にカレンダーを作ってみた
会社のイベントとして、2020年のアドベントカレンダーで記事を書いたのですが、個人のQiitaでも同じものを公開したいな〜と思ったので、ちょっとだけ変えて公開します!
去年の案件で急にReactでカレンダーを作ることになり、有識者もいない中、四苦八苦したのですが、ライブラリって便利だな〜と感じたので、Reactのreact-calendarを使ってカレンダーを作成したときのことを書きます。
※環境構築とかは端折ります。
react-calendarとは
カレンダー実装だともっとメジャーなライブラリがあるかも知れないんのですが、今回、私はreact-calendarを使ってみたのですが、簡単にReactでカレンダーを実装できちゃいました。
条件を絞って、日付ごとに任意の内容を表示させたり、スタイルのカスタマイズなど 柔軟に対応できます。
react-calendarで実装してみる
react-calendarをインストール
yarnで「react-calendar」をインストールします。
yarn add react-calendarreact-calendarをimportする
react-calendarをimportしてCalendarコンポーネントを呼び出します。
CalenderCmponent.jsximport Calendar from 'react-calendar' export default class Calendar extends Component { render() { return( <Calendar /> ) } }これだとまだ動きません!
カレンダーに日付を表示する
カレンダーのタイルに日付(日本時間)を表示します。
CalenderCmponent.jsximport Calendar from 'react-calendar' export default class Calendar extends Component { render() { return( <div> <Calendar locale="ja-JP" value={this.state.date} /> </div> ) } }それぞれのPropsについては公式だと以下のように説明されています。(英語のドキュメントのみ、、、)
Description Description Default value Example values locale Locale that should be used by the calendar. Can be any IETF language tag. User's browser settings "hu-HU" value Calendar value. Can be either one value or an array of two values. If you wish to use React-Calendar in an uncontrolled way, use defaultValue instead. "n/a" ・Date: new Date()
・An array of dates: [new Date(2017, 0, 1), new Date(2017, 7, 1)]これで、カレンダーの作成は完了です。とても簡単!!
カレンダーに任意のアイテムを表示してみる
これだけだと寂しいので、日付タイルに任意のアイテムを表示させてみたいと思います。
CalenderCmponent.jsximport Calendar from 'react-calendar' export default class Calendar extends Component { constructor(props) { super(props); this.state = { date: new Date(), //テストデータ month_item: { 2020-12-01: { text: 'work' }, 2020-12-10: { text: 'hangout' }, 2020-12-24: { text: 'Christmas Eve' }, 2020-12-25: { text: 'Christmas' }, } } }; //日付の内容を出力 getTileContent({ date, view }) { if (view === 'month') { const targetDate = moment(date).format('YYYY-MM-DD') return month_item[targetDate] && month_item[targetDate].text ? <div> <p>{month_item[targetDate].text}</p> </div> : null } } render() { return( <div> <Calendar locale="ja-JP" value={this.state.date} tileContent={this.getTileContent.bind(this)} //ここを追加 /> </div> ) } }tileContentの詳細の説明は以下です。functionを呼び出して、returnされたものを渡せば任意の日付に内容を表示させることができます。
Description Description Default value Example values tileContent Allows to render custom content within a given calendar item (day on month view, month on year view and so on). n/a ・String: "Sample"
・React element:
・Function: ({ activeStartDate, date, view }) => view === 'month' && date.getDay() === 0 ? It's Sunday! : nullまとめ
Reactって便利〜! 今回の記事では説明していませんが、Reduxと合わせてDBからデータを引っ張ってきてstateで制御すれば、スケジュールを登録したり、表示させたりすることも可能でした!
今回記載している以外にもたくさんPropsの種類もあるので、気になる方はぜひ公式URLから見てみてください!
参考
・React-Calendar が便利 | バシャログ。
・Reactでカレンダー作成(React-Calendarライブラリ)
- 投稿日:2021-01-18T18:58:30+09:00
初回renderを無視するuseEffect
はじめに
useEffectは特定の変数が更新された時だけ、中身を実行してくれる良いやつです。
ただ、初回のレンダー時はどんなことがあっても処理が走ってしまいます。
これを回避するのはなかなか面倒で、useRefを使ったりといろいろあるんですが、めっちゃ便利なライブラリ見つけたので紹介します。react-use
react-useは様々な便利系hooksが実装されていて、スター数も多くおすすめです。
https://github.com/streamich/react-use
この中で実装されているuseUpdateEffectが今回の目的のhooksです!使い方
使い方は猛烈に簡単!
useEffectと全く同じです。hooks名をuseUpdateEffectに変えればおしまい。
これだけで、初回のレンダー時に中身は実行されません。useUpdateEffect.tsuseUpdateEffect(() => { console.log(hoge) //初回は実行されない }, [hoge]);めっちゃ便利なのでぜひ使ってみてください〜。
- 投稿日:2021-01-18T18:19:58+09:00
【React初心者】 #3 ルーティング・ページ遷移を作る! react-router-dom
Reactでreact-router-domのルーティング設定
3回目の今回は、Reactでreact-router-dom
を使用したルーティングを使います。
URLを変えることで、表示するページを変える(レンダリングする)ことができます。前回までで、
- Material-UIで最低限のデザインを一から作成
- データをAPIから取得して表示
しました。コードは前回の続きとなります。
シリーズ記事
やること
- react-router-domでのReactでのルーティング
- ルーティングするためのリンクの付け方
react-router-dom導入
まず、インストールが必要です。
今回はreact-router-dom: 5.2.0
を使用しています。$ npm install react-router-domTypeScriptの場合は
npm install react-router-dom @types/react-router-dom
みたいです。とりあえず、aboutページを作成します。
ただ、文字を表示するだけにします。ということで、Aboutコンポーネントを作成。
$ touch src/components/About.js文字を表示するだけになります。
src/components/About.jsimport React from 'react' function About() { return ( <div> aboutページ </div> ) } export default About前回までのコードにRouterとSwitch、Routeを足して、
URLによって、レンダリングするコンポーネントを変えています。material-react/src/App.jsimport './App.css'; import { Grid } from '@material-ui/core'; import Header from './components/Header'; import Content from './components/Content'; import About from './components/About'; import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; function App() { return ( <Grid container direction="column"> <Grid item> <Header /> </Grid> <Grid item container> <Grid sm={2} /> <Grid xs={12} sm={8}> <Router> <Switch> <Route exact path="/"> <Content /> </Route> <Route path="/about"> <About /> </Route> </Switch> </Router> </Grid> <Grid sm={2} /> </Grid> </Grid> ); } export default App;とURLに入れると、
文字だけのAboutコンポーネントが表示されています。
説明と注意
<Route exact path="/">
のexact
は完全に一致しないとダメという意味です。一致するURLを上から探すのだけど、このexactをつけないと、
http://localhost:3000/jasdklfs
とか意味ないものやでもContent`コンポーネントを使うことになります。というか、
http://localhost:3000/
移行にURLに、何があろうがContent
コンポーネントを使います。試しに、exactを消して/aboutにアクセスしてみれば、
Content
コンポーネントが出るので、試してみてもいいと思います。
<Route path="/">
変更後、
http://localhost:3000/jasdklfs
とhttp://localhost:3000/about
と書いてみましょう。どちらも、Contentコンポーネントの内容が表示されます。
「書く順番」を考えましょう。URLのパラメータを取得して個別記事表示
前回、JSON Placeholderからデータを取得しました。
APIをみると、個別記事の取得もできるので、
記事についている「詳細を見る」ボタンを押したら遷移させます。個別記事のコンポーネントを作成
前回までで、左側は作りました。
今回は、右側を作ります。
個別記事IDにアクセスしたときにPostContentコンポーネントをレンダリングすることになります。
$ touch src/components/PostContent.js
/post/1
だったら記事1を表示するようにしてみます。とりあえず
idを受け取れるかテスト。
/post/1
にアクセスしたらuseParams()
に{id: 1}
のようなデータが入るのでid
だけをとるためconst { id }
にしています。そして、個別記事の後に受け取ったIDをくっつけて表示します。
material-react/src/components/PostContent.jsimport React from 'react' import { useParams } from 'react-router-dom' function PostContent() { const { id } = useParams(); return ( <div> <h1>個別記事 {id}</h1> </div> ) } export default PostContent
http://localhost:3000/post/1
にアクセスしたら1
が表示されているのでOK
http://localhost:3000/post/10
とかにしたら10
に変わりますので試してみてください。個別記事データをAPIで取得
Getで取得しましょう。URLの最後に記事のIDを入れればデータを取得できます。
では、JSON PlaceholderでAPIのURLは
{ "userId": 1, "id": 1, "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto" }axiosを使ったAPIのデータ取得は前回やったので簡単に書きます。
material-react/src/components/PostContent.jsimport React, {useState, useEffect} from 'react'; import axios from 'axios'; import { useParams } from 'react-router-dom' import { Grid } from '@material-ui/core' import BodyCard from './BodyCard' const cardContent = { avatarUrl: "https://joeschmoe.io/api/v1/random", imageUrl: "https://picsum.photos/150" } function PostContent() { const { id } = useParams(); const [post, setPosts] = useState([]) useEffect(() => { axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`) .then(res => { setPosts(res.data) }) }, []) return ( <Grid container spacing={2}> <Grid item xs={12} key={post.id}> <BodyCard {...{...post, ...cardContent}} /> </Grid> </Grid> ) } export default PostContent
https://jsonplaceholder.typicode.com/posts/${id}
でURLから取得したパラメータを埋め込んでGET、APIでの取得- useStateを使って、ページを開いた時一度だけ、APIで取得
- 前回作った、
BodyCard
コンポーネントに渡してMaterial-UIのカードの形式で作成
{...{...post, ...cardContent}}
で二つのオブジェクトを結合して渡してますとりあえず、ページを作り込むより、ルーティングの使い方説明メインなので、
- 詳細をみるというボタンがあったり、
- 画像がAPIでランダム取得するので変わってしまう、
- 画像がhight: 150pxだと大きさおかしい
一応、前回の記事から変更はないですが、BodyCardコンポーネントも載せておきます。
BodyCard.jsimport React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Card from '@material-ui/core/Card'; import CardActions from '@material-ui/core/CardActions'; import CardContent from '@material-ui/core/CardContent'; import Button from '@material-ui/core/Button'; import Typography from '@material-ui/core/Typography'; import CardHeader from '@material-ui/core/CardHeader'; import Avatar from '@material-ui/core/Avatar'; import IconButton from '@material-ui/core/IconButton'; import StarBorderOutlinedIcon from '@material-ui/icons/StarBorderOutlined'; import { CardMedia } from '@material-ui/core'; const useStyles = makeStyles({ bullet: { display: 'inline-block', margin: '0 2px', transform: 'scale(0.8)', }, title: { fontSize: 14, }, pos: { marginBottom: 12, }, cHeader: { height: '50px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', "& .MuiCardHeader-content": { overflow: 'hidden' } }, cContent: { height: '200px', overflow: 'hidden' } }); function BodyCard(props) { const { userId, id, title, body, avatarUrl, imageUrl } = props; const classes = useStyles(); const bull = <span className={classes.bullet}>•</span>; return ( <Card variant="outlined"> <CardHeader avatar={<Avatar src={avatarUrl} />} action={ <IconButton aria-label="settings"> <StarBorderOutlinedIcon /> </IconButton> } className={classes.cHeader} title={title} /> <CardMedia style={{ height: "150px" }} image={imageUrl} /> <CardContent className={classes.cContent}> <Typography variant="body2" component="p"> {body} </Typography> </CardContent> <CardActions> <Button size="small">詳細をみる</Button> </CardActions> </Card> ); } export default BodyCardボタンにリンクをつける
Material-UIにはButtonにhrefつければ遷移できるようになります。
以下のようにすれば大丈夫。BodyCard.js<Button size="small" href={`/post/${id}`}>詳細をみる</Button>今回、Buttonにリンク機能がついていたので使いましたが、
デザインを自分でやりたい、文字にリンクつけるなどの場合は、<Link to="/post/1">記事1の詳細</Link> <Link to=`/post/${id}`>記事{$id}の詳細</Link>などにすればいいと思います。
react-routerの公式サイトは英語ですが、
コードがみやすいのでみながら試してみてください。まとめ
- react-router-domでのReactでのルーティング
- ルーティングするためのリンクの付け方
をやりました。次回はAPIサーバーをPythonのDjangoで自作します。
補足
これまで、RouterをApp.jsに書きました。
ただ、ヘッダーがいらない、デザインを変えたいなどの場合(ログインページ、LP、認証のためのURLとか使う場合など)、
index.js
にrouterを書くこともできるので補足として。index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import About from './components/About'; import reportWebVitals from './reportWebVitals'; import { Route, BrowserRouter } from "react-router-dom"; ReactDOM.render( <React.StrictMode> <BrowserRouter> <div> <Route exact path="/" component={App} /> <Route exact path="/about" component={About} /> </div> </BrowserRouter> </React.StrictMode>, document.getElementById('root') );
- 投稿日:2021-01-18T17:58:51+09:00
【React hooks】噛み砕いて解説してみた~useContext編~
前書き
16.8v
で追加された機能であるreact hooks
を理解を深めるために体系的にまとめました。
- 【React hooks】噛み砕いて解説してみた~useState編~
- 【React hooks】噛み砕いて解説してみた~useEffect編~
- 【React hooks】噛み砕いて解説してみた~useContext編~ ←今ココ
以下、本題です
そもそもContextとは
別コンポーネントに値を動的に渡す方法として、
Props
があると思います。Context
も同じように値を動的に渡すことができます。Contextの存在意義
多重ネストされたコンポーネントに値を渡すときに、下記のようにバケツリレーになってしまうことが多々あると思います。(コードは公式ドキュメントからコピペしました)
<Page user={user} avatarSize={avatarSize} /> // ... Page コンポーネントは以下をレンダー ... <PageLayout user={user} avatarSize={avatarSize} /> // ... PageLayout コンポーネントは以下をレンダー ... <NavigationBar user={user} avatarSize={avatarSize} /> // ... NavigationBar コンポーネントは以下をレンダー ... <Link href={user.permalink}> <Avatar user={user} size={avatarSize} /> </Link>
Context
を使うことで、コンポーネントを超えて動的に値を渡すことができます。実際の使い方(hookなし)
まずは
useContext
を使わないパターンを解説していきます。そのほうが理解が深まるからです。(知っているよ!という人は飛ばしてください。)登場人物
createContext()
Proveider
Consumer
それぞれ軽く説明しておきます。
createContext()
const Context = React.createContext()この関数でコンテキストオブジェクトが作成されます。このコンテキストオブジェクトは下記のようになっています。
{ Provider: <>...<>, Consumer: <>...<>, ... }
Context.Provider
やContext.Consumer
のように使われます。実際に見ていきましょう。Provider
return ( <Context.Provider value={resource}> <Hoge /> </Context.Provider> )上記のように
Provider
のprops
のvalue
に渡したい値を指定してあげます。Consumer
const Hoge = () => ( <Context.Consumer> {(resource)=> ( <h1>{resource.title}</h1> )} </Context.Consumer> )上記のように実際に使いたいコンポーネント(今回は
Provider
ラップしたHoge
コンポーネント)でprops.child
に対して関数の引数で渡したい値(ここではresource
)を入れています。以上で
useContext
を使わないパターンの説明は終了です!以降から
useContext
を使ったパターンの解説をしていきます。useContextを使う場合
簡潔にお伝えすると
Consumer
、つまり実際に値を呼び出すコンポーネントで使うことができます。useContext
を使わない場合は下記のような形ですね。const Hoge = () => ( <Context.Consumer> {(resource)=> ( <h1>{resource.title}</h1> )} </Context.Consumer> )
useContext
を使う場合は下記のようにuseContext()
関数の引数にコンテキストオブジェクトを含めてください。const Hoge = () => { const { resource } = useContext(Context) return ( <h1>{resource.title}</h1> ) }
props.child
が不要になったのでスッキリしましたね!解説は以上です。お疲れ様でした?♂️
参考記事
- 投稿日:2021-01-18T17:40:27+09:00
React+reduxで数字をカウントしてみる
index.jsを用意します
import React from 'react'; import ReactDOM from 'react-dom'; import { createStore } from 'redux'; import { Provider } from 'react-redux'; import { persistStore, persistReducer } from 'redux-persist' import storage from 'redux-persist/lib/storage' import { PersistGate } from 'redux-persist/integration/react' import './index.css'; import App from './App'; // ステートの値 let state_value = { counter:0, message:"COUNTER" } // レデューサー function counter(state = state_value, action) { switch (action.type) { case 'INCREMENT': return { counter:state.counter + 1, message:"INCREMENT" }; case 'DECREMENT': return { counter:state.counter - 1, message:"DECREMENT" }; case 'RESET': return { counter:0, message:"RESET" }; default: return state; } } // Redux Persistの設定 const persistConfig = { key: 'root', storage, } // パーシストレデューサーの作成 const persistedReducer = persistReducer(persistConfig, counter) // ストア、パーシスターの作成 let store = createStore(persistedReducer) let pstore = persistStore(store) // 表示をレンダリング ReactDOM.render( <Provider store={store}> <PersistGate loading={<p>loading...</p>} persistor={pstore}> <App /> </PersistGate> </Provider>, document.getElementById('root') );ここではパーシストレデューサーをもとに、ストアとパーシスターを作成しました。
次にパーシスターとAPPコンポーネントをJSXで表示します。APP.jsを作成
import React, { Component } from 'react'; import { connect } from 'react-redux'; import './App.css'; // Appコンポーネント class App extends Component { constructor(props){ super(props); } render() { return ( <div> <h1>Redux</h1> <Message /> <Button /> </div> ); } } // ストアのコネクト App = connect()(App); // メッセージ表示のコンポーネント class Message extends Component { style = { fontSize:"20pt", padding:"20px 5px" } render(){ return ( <p style={this.style}> {this.props.message}: {this.props.counter} </p> ); } } // ストアのコネクト Message = connect((state)=>state)(Message); // ボタンのコンポーネント class Button extends Component { style = { fontSize:"16pt", padding:"5px 10px" } constructor(props){ super(props); this.doAction = this.doAction.bind(this); } // ボタンクリックでディスパッチを実行 doAction(e){ if (e.shiftKey){ this.props.dispatch({ type:'DECREMENT' }); } else if (e.ctrlKey){ this.props.dispatch({ type:'RESET' }); } else { this.props.dispatch({ type:'INCREMENT' }); } } render(){ return ( <button style={this.style} onClick={this.doAction}> click </button> ); } } // ストアのコネクト Button = connect()(Button); export default App;APP.js内に画面に表示するMessageコンポーネントとプッシュボタンのButtonコンポーネントを作成します。
Buttonコンポーネント内には、ボタンを押すINCREMENT、Shiftキー押しながらボタンを押すとDECREMENT、ctrlキーを押しながらボタンを押すとRESETのタイプを作りました。ボタンを押すとタイプごとのアクションをRedux送りそのタイプにあった処理が行われます。読んでくださりありがとうございます。
初学者なので至らないところがあると思います。
おかしい所があればご指摘願います。
- 投稿日:2021-01-18T15:53:34+09:00
【TypeScript】.ts拡張子と.tsx拡張子
.ts拡張子と.tsx拡張子の違い
.ts拡張子
- 純粋なTypeScriptファイル
- JSX要素の追加をサポートしない
.tsx拡張子
- JSXを含むファイル
- 型アサーションの記法として
value as type
と<type>value
の2通りあるが、後者は.tsx
には書けない
(<>
はJSXタグのマーカーであるため)Reactを使うプロジェクト内のTypeScriptファイルは全て.tsxではダメなのか
結論
.ts
と.tsx
は明示的に分けるべき理由
- 拡張子で明示的にしておくことで、「このファイルにJSXを書くべきではない」ことを表すことができる
(UIコンポーネントのファイルか、ロジックを書くファイルか)
- 投稿日:2021-01-18T14:42:36+09:00
Emotion v11とtypescriptが喧嘩する ~jsx設定方法~
emotionの環境構築をおこなったところ、
TypeScript絡みの設定が案外手強かったので備忘録とさせていただきます。ざっくり環境
React v17.0.1
typescript v4.1.3
@emotion/react v11.1.4
@babel/core v7.12.10jsx属性認識しない問題
import { jsx } from '@emotion/react' import { FooterContainer } from '../../../style/components/block/Footer' export default function Footer() { return ( <div css={FooterContainer}> <p>Footer</p> </div> ) }emotion v11においては、
上記のようにjsx
をimportして、reactからやってくるjsxを使わない(reactを直でimportしない)記述となります。
しかし、早速問題が...なんとか型定義をimportしているjsxの方に向かせたいですね。
そもそもreactのJSXの型定義はどこからきているのでしょうか?答えはTypeScriptのコンパイルのオプションである
jsxFactory
からきています。
https://www.typescriptlang.org/tsconfig/#jsxFactory
こちらはデフォルトではReact.createElement
になっています。
jsxFactory
をemotionのjsxで呼べるよう変更しておきます。
ちなみにトランスパイルをbabelに任せている場合、"jsx": "preserve"
でOKです。tsconfig{ "compilerOptions": { "jsx": "preserve", "jsxFactory": "jsx", ... }これでエラーが解消されます。
別解:プラグマを使う
公式のドキュメントでは以下のようなコメントが差し込まれています。
/** @jsx jsx */このコメントはプラグマというTSでサポートされている機能になります。
この機能は、ファイル毎にjsxFactoryを変更できる機能です。
こちらを追加するとエラーが解消されます。/** @jsx jsx */ import { jsx } from '@emotion/react' import { FooterContainer } from '../../../style/components/block/Footer' export default function Footer() { return ( <div css={FooterContainer}> <p>Footer</p> </div> ) }https://github.com/Microsoft/TypeScript/pull/21218
ただ、ファイル毎にプラグマを記述しないといけないのが微妙ですね。
わがまま
jsxを直で上書きしてしまうのはちょっと不安です。
毎度jsxを読まないといけないのもなんか微妙です。
もっといい方法があれば教えていただけると嬉しいです
- 投稿日:2021-01-18T12:29:43+09:00
Redux超入門【React】
Reduxが難しかったのですが、1つのファイルにまとめてみるとすんなり理解できたので記載。
準備
まずはReactの環境構築。
$ npx create-react-app アプリ名次にカレンドディレクトリに移動してreduxの導入。
$ npm install redux react-redux // npm→yarnでも可コード
index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; import {createStore} from 'redux'; // Action const increment = () => { return { type: 'INCREMENT' } } const decrement = () => { return { type: 'DECREMENT' } } // Reducer const counter = (state = 0, action) => { switch(action.type){ case 'INCREMENT': return state + 1 case 'DECREMENT': return state - 1 } } let store = createStore(counter) // Display it in the console store.subscribe(() => console.log(store.getState())) // Dispatch store.dispatch(increment()) store.dispatch(decrement()) store.dispatch(increment()) ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') );
- 投稿日:2021-01-18T09:32:06+09:00
React Hooks ~ Effect Hookの使い方 ~
Effect Hook とは
レンダリング後の DOM の変更や、状態の更新、他コンポーネントに対して影響を及ぼす処理(effects)を制御するための Hooks API になります。
基本的な書き方は下記、 useState と同様に import して関数コンポーネント内に記述することです。
import React, { useEffect } from "react" // 関数コンポーネント内で下記を記述する useEffect(() => { // 処理をここに書く // 【option】関数を返すようにすると、アンマウントされる前に関数が実行される。 return () => console.log('クリーンアップを実行'); }, []);Effect Hook は以下のタイミングで実行される。
- レンダリング後に必ず実行する。
- 初回のレンダリング後だけ実行する。
- 指定したデータに変更があった場合のみ実行する。
- 例で示した文の
[]
のところに、何の変更をトリガーにするかを書くことができる。利点として、State など、Reactの機能をEffect Hookの関数内で利用できること。
Effect の利用ケース
- APIとの通信
- State を設定して、レンダリング後に API からデータを取得してセットする。
- Stateを変更させたときに、他に影響を与える場合
- 例えばチャットアプリで相手から返信が来た時に、タイトルに未読件数を表記するとか
サンプル
State の変更を受け取って、ページのタイトルを変更する Effect Hook を実装します。
記事投稿サイトみたいなのをイメージしてます。App.tsximport React, { useState, useCallback, useEffect } from "react"; import { InputForm } from "./InputForm"; import "./styles.css"; export default function App() { const [content, setContent] = useState<string>("test"); const onChangeContent = useCallback( (event: React.ChangeEvent<HTMLInputElement>) => { setContent(event.target.value); }, [] ); useEffect(() => { document.title = `${content} が入力されている。`; console.log("content が変更された"); }, [content]); return ( <div className="App"> <InputForm content={content} onChange={onChangeContent} /> <div> <span>content の値は、"{content}"</span> </div> </div> ); }InputForm.tsximport React from "react" interface Props { content: string; onChange: (event: React.ChangeEvent<HTMLInputElement>) => void; } export const InputForm = (props: Props) => { return <input defaultValue={props.content} onChange={props.onChange}></input> }簡単ではありますが、codesandboxにサンプルのコードを上げてます。
余分な関数の実行や、知らぬ間に DOM が書き換わってしまっている、を防ぐために、第二引数はなるべく入れとく方が良いのかなと思ったりしました。その他
- useEffect は1つのコンポーネント内で複数設定が可能
- 異なる変数を第二引数に設定するなどもできる
- 投稿日:2021-01-18T08:56:23+09:00
Next.jsでSSRを簡単にするには 其の壱「旅立ち」
Next.jsでSSRを簡単にするには 其の壱「旅立ち」
こちらに同じ内容を書いています
1. Next.jsでのSSRはApolloが優秀
検索Botの性能が微妙な頃、SPAのWebシステムではSSRが重要な位置を占めていました。みんな必死にSSRを行うための取り組みを行いました。しかしクライアントで動くべき物をサーバで実行し、その結果を返すというのは、それなりの無理が生じます。無理を通しているが故に、開発コストに跳ね返ってしまうのです。
コストを抑えつつSSRを導入しようとした場合、GraphQLとApollo導入が無難な流れとなります。Apolloのキャッシュ機能を利用すれば、サーバ側で生成したデータを短いステップでクライアントに引き継ぐことが出来るからです。一回手続を書いておけば、その他の部分ではクライアントとサーバのコードを別々に書く必要はありません。
ではGraphQLを使わなかった場合はどうでしょう?選択肢を調べてみましたが、Apolloのようにサーバ側で生成したキャッシュをまとめて引き渡すような便利なライブラリが見つかりませんでした。大抵の場合、地道に部分部分で引き渡しコードを書いている形です。
2. Next.js上でSSRをする時に使う物
- _app.tsx上でApp.getInitialProps
- 各Page上でgetInitialProps
- 各Page上でgetServerSideProps
現時点で推奨されているのはgetServerSidePropsです。サーバでしか実行されず、その実行結果はクライアント側に送られます。ただこれ、デフォルトだとSPAのページ切り替えごとにサーバにデータを取りに行きます。一応、shallowパラメータを設定すれば再ロードを回避することは可能なのですが、propsには古いデータが残ってしまい、キャッシュの更新判定が面倒なことになります。ぶっちゃけて言うと、キャッシュのことを考えずに、とにかくデータが受け取れれば良いという場合にしか使い道がありません。
各Page上でgetInitialPropsの方も、ページを作るごとに個別に受け渡し処理を作成するのも面倒です。
やはり理想はApolloのApp.getInitialPropsを使ったキャッシュ引き渡しです。Next.jsでのビルド時に、「推奨していないApp.getInitialPropsをまだ使ってるの?getServerSideProps使いなよ」と文句を言われますが、まともな選択肢が他にないのです。
3. useSWRを試してみる
とりあえず上手いやり方は無いかと使い勝手が良いと話題のuseSWRを試してみました。fetchのURLをキーにして、取得データをキャッシュすることが出来ます。しかも取得したキャッシュに自由にアクセスが可能です。これはと思ったのもつかの間、以下の問題により断念しました。
- useSWRは適度なタイミングでデータをとりにいく事が目的で、必要ないときはデータをとらないという機能が無い
- dedupingIntervalに大きな値を設定すれば一応データを取りに行かなくなるけど、とらないフラグ付けて
- タイミングが色々指定できるし便利だけど、今欲しいのはそれじゃない
- そして最も重要なポイント、cacheで自由にキャッシュを書き換えることは出来た、確かに出来た。しかしfetchの戻り値を個別管理していて、次のターンで復元される
useSWRのソースを眺めてみた結果、cacheにはアクセスできるけど、データの流れ的にfetchの戻り値をがっちり持っていて、根本を書き換えないと理想的な動作にならないことが判明。
4. useSWRのような使い勝手でApolloみたいなことをするには
結論「自分で作るしかない」
ということで願いを叶えるため、自前のuseSWRのHooksの構造とApolloキャッシュ構築方法をパクり、オレオレuseFetchを作りはじめした
現在の進行状況(基本的な実装は完了)
天気予報を取得しSSRしつつキャッシュを保持のサンプル
https://github.com/SoraKumo001/use-fetch-sample01次回 Next.jsでSSRを簡単にするには 其の弐「流浪」
- 投稿日:2021-01-18T03:43:06+09:00
Material-UI でデフォルトテーマを変更する方法
概要
React とセットで使うことの多い Material-UI のテーマを変更する方法です。
デフォルトテーマは『こちら』を参照してください。テーマをカスタマイズ
ここでは
theme.js
というファイルを作成します。theme.jsimport { createMuiTheme } from '@material-ui/core/styles'; // フォントを設定 const fontFamily = [ 'Noto Sans JP', 'メイリオ', 'MS Pゴシック', 'sans-serif', // 使用したいフォントを以降に羅列してください ].join(','); /***************** * テーマを設定 ***************** */ const theme = createMuiTheme({ typography: { fontFamily: fontFamily, // フォント }, palette: { // Primaryカラーを設定 primary: { light: '#54C527', main: '#ff9800', dark: '#b26a00', contrastText: '#000000', mainGradient: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)', }, // Secondaryカラーを設定 secondary: { light: '#33eb91', main: '#00e676', dark: '#00a152', contrastText: '#ffffff', }, type: 'dark', // ダークモードをON }, mixins: { // ツールバーの高さ toolbar: { minHeight: 64, }, }, // 各パーツのスタイルをカスタマイズ props: { MuiCheckbox: { color: 'primary', }, MuiList: { dense: true, }, MuiTable: { size: 'small', }, MuiTextField: { variant: 'outlined', }, }, }); export default theme;マテリアルデザイン仕様のカラーは『こちら』で確認することができます。
テーマをコンポーネントに適用
以下の例は create-react-app で作成されたindex.jsに適用しています。
ポイントはMuiThemeProvider
をimportしてコンポーネントに適用してあげることです。
パラメーターに先ほど作成したtheme.jsを渡してあげてください。index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; // カスタムしたテーマ(スタイル)を定義 import { MuiThemeProvider } from '@material-ui/core/styles'; import theme from './styles/theme'; ReactDOM.render( <React.StrictMode> <MuiThemeProvider theme={theme}> <App /> </MuiThemeProvider> </React.StrictMode>, document.getElementById('root') ); reportWebVitals();
- 投稿日:2021-01-18T03:43:06+09:00
Material-UI でデフォルトテーマをカスタマイズする方法
概要
React とセットで使うことの多い Material-UI のテーマをカスタマイズする方法です。
デフォルトテーマは『こちら』を参照してください。テーマをカスタマイズ
ここでは
theme.js
というファイルを作成します。theme.jsimport { createMuiTheme } from '@material-ui/core/styles'; // フォントを設定 const fontFamily = [ 'Noto Sans JP', 'メイリオ', 'MS Pゴシック', 'sans-serif', // 使用したいフォントを以降に羅列してください ].join(','); /***************** * テーマを設定 ***************** */ const theme = createMuiTheme({ typography: { fontFamily: fontFamily, // フォント }, palette: { // Primaryカラーを設定 primary: { light: '#54C527', main: '#ff9800', dark: '#b26a00', contrastText: '#000000', mainGradient: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)', }, // Secondaryカラーを設定 secondary: { light: '#33eb91', main: '#00e676', dark: '#00a152', contrastText: '#ffffff', }, type: 'dark', // ダークモードをON }, mixins: { // ツールバーの高さ toolbar: { minHeight: 64, }, }, // 各パーツのスタイルをカスタマイズ props: { MuiCheckbox: { color: 'primary', }, MuiList: { dense: true, }, MuiTable: { size: 'small', }, MuiTextField: { variant: 'outlined', }, }, }); export default theme;マテリアルデザイン仕様のカラーは『こちら』で確認することができます。
テーマをコンポーネントに適用
以下の例は create-react-app で作成されたindex.jsに適用しています。
ポイントはMuiThemeProvider
をimportしてコンポーネントに適用してあげることです。
パラメーターに先ほど作成したtheme.jsを渡してあげてください。index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; // カスタムしたテーマ(スタイル)を定義 import { MuiThemeProvider } from '@material-ui/core/styles'; import theme from './styles/theme'; ReactDOM.render( <React.StrictMode> <MuiThemeProvider theme={theme}> <App /> </MuiThemeProvider> </React.StrictMode>, document.getElementById('root') ); reportWebVitals();