//tips
嶋津様との打ち合わせでgit push -u origin mainでアップできることがわかった。無事にgithubにもアップできるようになってよかった。
//smart contract
npm install swrでネットワーク経由で値を取得する独自フックを追加。
Data.jsonからswrを利用してデータを取得して表示してみる。const { data } = useSWR('/data.json’)でdata.jsonからデータを取得しており、これはステートであることからデータが更新されればdataの表示にも反映される。
次に const { data, err } = useSWR('/data.txt', func)とし、テキストファイルの読み込みを実施してみる。ここではアクセスに使う関数を引数に指定している。jsonフォーマットではないので指定が必要となっている。
res.text())でresponseから取得したデータをテキストのまま返す。このようにswrは関数を用意することであらゆるフォーマットのデータにも対応できる。
import Layout from '../components/layout'
import useSWR from 'swr'
export default function Home() {
const func = (...args)=> fetch(...args).then(res => res.text())
const { data, err } = useSWR('/data.txt', func)
return (
<div>
<Layout header="Next.js" title="Top page.">
<div className="alert alert-primary text-center">
<h5 className="mb-4">
{ data }
</h5>
</div>
</Layout>
</div>
)
}
ここからapiの世界に入る。
next.jsのサンプルとして、http://localhost:3000/api/helloに{"name":"John Doe"}が用意されており、
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}
このように書かれている。apiを関数として作成し、それをexport defaultでエクスポートすればapiのスクリプトとなる。
ちなみにres.status(200)とは、正常にアクセスできたことを示す番号。
簡単なapiを作成してみる。
import apidata from '../../components/data'
export default function handler(req, res) {
let id = req.query.id
if (id == undefined) { id = 0 }
if (id >= apidata.length) { id = 0 }
res.json(apidata[id])
}
http://localhost:3000/api/hello?id=1とするとデータベースから簡単な項目を引き出すことができた。 let id = req.query.idが?id=1のところに該当する。?id=1はrequestのqueryプロパティにまとめられるのでidキーの値として取り出すことができる。
うまく表示ができずswrの公式書類などを探りながら問題解決。const { data, err } = useSWR(address)としてしまうとdataで何も返されなくなってしまうのでfetcherを定義して加える必要があった。
export default function Home() {
const fetcher = url => fetch(url).then(r => r.json())
const [ address, setAddress ] = useState('/api/hello')
const { data, err } = useSWR(address,fetcher)
参考:
https://teratail.com/questions/365179
Setaddressでaddressを変更すればswrによりそのアドレスからデータをdataステートに取り出されるようになる。
入力フィールドではonchnage属性に以下のような関数を設定している。
const onChange = (e)=> {
setAddress('/api/hello?id=' + e.target.value)
}
<input type="number" className="form-control"
onChange={onChange} />
?id=1のアクセス部分を改善。ここは[id].jsというファイルを用意して/hello/1のような形でアクセス可能にする。
Api/hello/id番号という形でのアクセスを可能にするように変更。[id].jsフォルダを作成。
import apidata from '../../../components/data'
export default function handler(req, res) {
const {
query: {id}
} = req
res.json(apidata[id])
}
これで数字の記入部分をquery: {id}に代入して、resの表示を指定idのjson形式で返せるようにしている。
Reqのqueryにある値をidに取り出す分割代入と呼ばれる形式で、reqのquery.idが{query:{id}}という形で代入されている。
Index.jsも下記の形に変更。
const onChange = (e)=> {
setAddress('/api/hello/' + e.target.value)
}
さらにhello/番号/名前のような形でアクセスして番号と名前を取得したい場合には、配列ファイルというものを用意する必要があり、[…名前].jsというふうにファイル名を指定することになる。
これはhello以降のファイルパスを配列の形にして取り出せるようにするもの。[“hello”,“番号”,“名前”]のような形になる。
import apidata from '../../../components/data'
export default function handler(req, res) {
const {
query: {params: [id, item]}
} = req
const result = {id: id, item: apidata[id][item]}
res.json(result)
}
これで/api/hello/1/mailというアドレスにアクセスすると{"name":"hanako","mail":"hanako@flower","age":28}の中から
{"id":"1","item":"hanako@flower”}を抜き出して指定したmailキー部分の値をitemという形で表示してくれる。
入力URLからそのまま指定できるのでかなり直感的に分かりやすい。
const {
query: {params: [id, item]}
} = req
で/1/mailへのアクセスが[1,”mail”]で取り出されていることがわかる。reqのqueryにparamsプロパティが用意され、そちらに配列が渡される。
ここで重要なのはreqから代入されるquery内の項目はファイル名と同じものである点で、[…params].jsでのファイル作成により、クエリパラメータはparamsという配列に渡されるようにできたので、このように抜き出せている。
これをindex.jsに適用させるとリアルタイムの数値変更で表示が変わるようにできる。
import {useState} from 'react'
import Layout from '../components/layout'
import useSWR from 'swr'
export default function Home() {
const fetcher = url => fetch(url).then(r => r.json())
const [pref, setPref] = useState({id:0, item:'name'})
const [ address, setAddress ] = useState('/api/hello/'
+ pref.id + '/' + pref.item)
const { data, err } = useSWR(address,fetcher)
const onChange = (e)=> {
pref.id = e.target.value
setPref(pref)
setAddress('/api/hello/' + pref.id + '/' + pref.item)
}
const onSelect = (e)=> {
pref.item = e.target.value
setPref(pref)
setAddress('/api/hello/' + pref.id + '/' + pref.item)
}
return (
<div>
<Layout header="Next.js" title="Top page.">
<div className="alert alert-primary text-center">
<h5 className="mb-4">
{JSON.stringify(data) }
</h5>
<input type="number"
className="form-control form-control-sm mb-2"
onChange={onChange} />
<select onChange={onSelect}
className="form-control form-control-sm">
<option value="name">Name</option>
<option value="mail">Mail</option>
<option value="age">Age</option>
</select>
</div>
</Layout>
</div>
)
}
SWRのアドレスに設定したステートを更新すれば取得されるデータも更新される。
プログラマブル電卓を作成する。
まずは、func.jsモジュールを作成し、そちらを読み込ませるようにする。
形式が特殊で、関数のデータをオブジェクトにまとめている。
{‘func’:{…関数データ…}}
この関数データの中身は、
‘名前’:{‘caption’:キャプション,‘function’:関数定義のテキスト}
となっており、名前をキーに2つのオブジェクトを用意している。
'tax': {
'caption': '入力した金額から消費税(10%)価格を計算します。',
'function': '(...param)=> { return Math.floor(param[0] * 1.1) }'
},
をみるとわかるようにキャプションは機能の説明、functionの方が処理内容であることがわかる。
この関数処理をどのように実行するかが大事なところで...param)=> で入力した値が配列として渡され、それに対して関数を実行し、returnで結果を返す。関数部分も’’で囲みテキストで用意する点に注意。
export default {
'func': {
'tax': {
'caption': '入力した金額から消費税(10%)価格を計算します。',
'function': '(...param)=> { return Math.floor(param[0] * 1.1) }'
},
'tax2': {
'caption': '入力した金額から軽減税率(8%)による税込価格を計算します。',
'function': '(...param)=> { return Math.floor(param[0] * 1.08) }'
},
'total': {
'caption': '10,20,30...というようにカンマで区切った数字の合計を計算します。',
'function': `(...param)=> {
let re = 0
for (let i in param) {
re += param[i] * 1
}
return re
}`
},
'factorial': {
'caption': 'ゼロから入力値までの合計を計算します。',
'function': `(...param)=> {
let re = 0;
for(let i = 0;i <= param[0];i++){
re += i
}
return re
}`
},
}
}
FuncモジュールををApiにするためにpagesフォルダの中のapiフォルダにfunc.jsを作成する。もちろんそのままimportする方法も使える。
import func from '../../components/func'
export default function handler(req, res) {
res.json(func)
}
これは単にオブジェクトをjsonフォーマットにするもの。
電卓の本体部分となるcalcコンポーネントも用意。
export default function Calc(props) {
const [message, setMessage] = useState('')
const [input, setInput] = useState('')
const [data, setData] = usePersist('calc-history', [])
const [func, setFunc] = useState({func:{}})
履歴データのdataのみローカルストレージに保管するためpersistフックで作成し、他はステートフック。
関数データのfuncのみは先に作成したCalcから取得。
const fetchFunc = (address)=>
fetch(address).then(res => res.json())
Data更新時のみ再度設定の実行。useeffectを使用することで操作の自動化と更新の必要がない場合のスキップを行なっている。
useEffect(() => {
fetchFunc('/api/func').then((r)=>{
setFunc(r)
})
},[data])
Enterキーを押して処理を実行させる処理はdoaction関数を使い、下記のように書けるが、入力内容に関数を適用するのはconst res = eval(input)が大きな役割を果たしており、evalは引数に指定したテキストをjsスクリプトとして実行するもの。先に関数をテキストで書いたのはこれを利用するため。最後のsetInput(‘’)は入力フィールドをクリアしている。
data.unshiftは履歴データの一番上に現在実行した情報を追加。setDataで履歴を更新。
// Enter時の処理
const doAction = (e)=> {
const res = eval(input)
setMessage(res)
data.unshift(input + ' = ' + res)
setData(data)
setInput('')
}
関数の選択はどうなっているかというと下記で表される。const fid = e.target.idでターゲットのidを取り出し、クリックしたボタンに対応する関数データをfunc.func[fid]で取り出し、eval(f.function)でテキストで書かれた関数をjsで実行している。 data.unshift(fid + ' = ' + res)が実際に結果として表示されているもの。
// 関数ボタンの処理
const doFunc = (e)=> {
const arr = input.split(',')
const fid = e.target.id
const f = func.func[fid]
const fe = eval(f.function)
const res = fe(...arr)
setMessage(res)
data.unshift(fid + ' = ' + res)
setData(data)
setInput('')
}
ややこしいのは、関数データをもとにボタンを生成するため、{Object.entries(func.func).map((value,key)=>(として行うことになること。
Jsonで渡されるデータは配列ではなくオブジェクトなので、配列で利用できるmapが使えず、Object.entriesとして一旦配列に変換し、そこからmapで呼び出す方法をとっている。
Object.entries(オブジェクト).map((関数)
Object.entriesの役割は分かりやすく{a:b…}のところを[[a,b]…]に変換しているだけ。func.funcにある関数データを配列化しており、[関数名,関数データ]という形に変換される。こうすると関数名はvalue[0]、関数データのオブジェクトにあるcaptionはvalue[1].captionとなる。
{Object.entries(func.func).map((value,key)=>(
<button className="btn btn-secondary m-1" key={key}
title={value[1].caption} id={value[0]}
onClick={doFunc} >{value[0]}</button>
))}
ここ方はfirebaseというデータベースサービスとユーザ認証機能であるAuthenticationをアプリに組み込む。
Firebaseはサーバレスでデータを扱えるサービスで、クラウド環境にデータファイルやデータベースなどを設置し、インターネット経由でアクセスしてそれらを利用できるようにしている。
Firebaseにプロジェクトを作成。ついでwebアプリにfirebaseを追加という項目を選択。ここでfirebase SDKを利用するためのコードを取得。
Firebaseにはcdnを利用する方法とnpmでパッケージ取得する方法がある。