了解了 React useState 與 useEffect 掛鉤,現在來實作一個可以實際應用的範例。
我們要使用先前建立的 WebSocket Service and RabbitMQ 即時訊息通知 來建立一個 Web App 即時訊息通知功能。透過這個 WebScocket 伺服器可以讓你從任何地方送出訊息,並即時顯示在你的 APEX Web Application 上。
加入基本的原始碼 建立一個新的 APEX Page,在 Page Properties 的 JavaScript File URLs 加入三個原始碼。
File URLs 1 2 3 https://unpkg.com/react@16.13.1/umd/react.development.js https://unpkg.com/react-dom@16.13.1/umd/react-dom.development.js https://unpkg.com/@babel/standalone/babel.min.js
應用程序模組 然後在 Page Properties 的 JavaScript Function and Global Variable Declaration 加入我們自己的程式碼。
Demo module 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 var Demo = (( ) => { const uuid = () => { const s4 = () => Math .floor((1 + Math .random()) * 0x10000 ).toString(16 ).substring(1 ); return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4() }; const toLoalISOString = (date ) => { var tzo = -date.getTimezoneOffset(), dif = tzo >= 0 ? '+' : '-' , pad = function (num ) { var norm = Math .floor(Math .abs(num)); return (norm < 10 ? '0' : '' ) + norm; }; return date.getFullYear() + '-' + pad(date.getMonth() + 1 ) + '-' + pad(date.getDate()) + 'T' + pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds()) + dif + pad(tzo / 60 ) + ':' + pad(tzo % 60 ); }; const roles = { "roleDemo" : { token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3c3NpZCI6IjAwMDEzY2Y1LTcxOWItNDAxZC05YWUyLWRjZjJkYTVlZTRmYyIsIk5hbWUiOiJyb2xlRGVtbyIsImlhdCI6MTU5NjQ0MDYxNH0.KYIuu2xqITmStn1Yy1cu2xuGOcd1CuXYKpLGAU9OmhI' , wssid: '00013cf5-719b-401d-9ae2-dcf2da5ee4fc' }, "roleOne" : { token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3c3NpZCI6ImY0YTk0MDkxLTgxYWItNDRiOC1hZWNlLTYzNTI2ZWU4OGFlNiIsIk5hbWUiOiJyb2xlT25lIiwiaWF0IjoxNTk2NDQwNzA3fQ.IFdERyYpwAs3zLx9eUznqLdbuq1xjLF3snw3etgbTpQ' , wssid: 'f4a94091-81ab-44b8-aece-63526ee88ae6' }, "roleOther" : { token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3c3NpZCI6ImRlYjg4N2ZhLTNlODQtNGEyYi1hNzZmLTA2OWY4MzU0ZmE0MyIsIk5hbWUiOiJyb2xlT3RoZXIiLCJpYXQiOjE1OTY0NDA3ODd9.jSnLlUeYfCpCR7_bg5nlPeQGKwFtI8IEa-NPsWfgtIw' , wssid: 'deb887fa-3e84-4a2b-a76f-069f8354fa43' } }; const dataSample = { id: uuid(), title: 'Title 標頭' , message: 'Hello, Tainan. 自己從瀏覽器送出的訊息。' }; const socket = (roleName = "roleDemo" ) => { const { token } = roles[roleName]; const url = `ws://10.11.25.138:4040/?token=${token} ` ; const ws = new WebSocket(`ws://10.11.25.138:4040/?token=${token} ` ); const subscribe = (callback ) => { ws.onmessage = ({ data } ) => { try { const parsedData = JSON .parse(data); callback(parsedData); } catch (err) { console .log(err); callback(null ); } }; }; const sendTo = (roleName = "roleDemo" , data = dataSample ) => { const { wssid } = roles[roleName]; const message = { wssid, ...data, created : Date .now() }; return ws.send(JSON .stringify(message)); }; return { subscribe, sendTo }; }; return { uuid, toLoalISOString, socket, ...Demo }; })(Demo || {});
這裡列出 3 個可用的測試角色,你可以申請自己的 token 與 wssid。這個範例預設會使用 roleDemo 訂閱 WebSocket Service。sendTo 函式會用來測試送出訊息,預設也是會送給 roleDemo 自己。也用一個預設的訊息範例 dataSample。這些訊息的資料欄位請視你的所需自行調整。
現在可以測試一下我們的模組。開啟瀏覽器的 Console:
1 2 3 4 5 6 7 const wss = Demo.socket(); wss.subscribe(console.log); wss.sendTo(); {"wssid":"00013cf5-719b-401d-9ae2-dcf2da5ee4fc","id":"e5adf55e-1949-45c7-8cd5-d4c47ae1bb28","title":"Title 標頭","message":"Hello, Tainan. 自己從瀏覽器送出的訊息。","created":1602638703748}
React 組件 建立一個新的 Region,我們現在可以在 Region Properties Source 的 Text 中加 React 程式碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <div id ="react-container" > </div > <script type ="text/babel" > const { useState, useEffect } = React; const { render } = ReactDOM; const { toLoalISOString, socket } = Demo; const wss = socket("roleDemo" ); const useWssMessages = () => { const [messages, setMessages] = useState([]); const addMessage = message => setMessages(allMessages => (message ? [message, ...allMessages] : allMessages)); useEffect(() => { wss.subscribe(addMessage); }, []); return messages; }; const Message = ({title, message, created} ) => { return ( <p>Title: {title} Content: {message} AT: {toLoalISOString(new Date (created))}</p> ); }; const MessageList = () => { const messages = useWssMessages(); if (!messages.length) return <div > No Messages Listed.</div > return ( <> <p > Messages: {messages.length}</p > { messages.map((message, i ) => <Message key ={i} {...message } /> ) } </> ); }; const App = () => ( <> <h3 > WebSocket instant messages</h3 > <MessageList /> </> ); render( <App /> , document .getElementById("react-container" ) ); </script >
開啟瀏覽器的 Console:
你的 APEX Page 畫面應該馬上會顯示訊息。
使用 GraphQL API 送出訊息 透過 GraphQL API ,其實你可以從任何的地方使用 http 協定送出即時訊息。
可以使用 GraphQL PlayGround 送出測試資料。
GrlphQL API 1 2 3 4 5 6 7 8 9 10 11 // url: GraphQL http://10.11.25.138:4000/v1/graphql // Mutation mutation sendToQueue($queue: String!, $message: String!) { sendToQueue(queue: $queue, message: $message) } // Query Variables {"queue":"wss.notification.demo", "message":"{\"wssid\":\"00013cf5-719b-401d-9ae2-dcf2da5ee4fc\",\"title\":\"Hello Tainan! 台南!\",\"message\":\"GraphQL http://10.11.25.138:4000/v1/graphql API\",\"created\":1602641256154}" }
現在從 Oracle 資料庫透過 GraphQL API 送出訊息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 declare l_http_request Utl_Http.req; l_http_response Utl_Http.resp; l_url varchar2(255) := 'http://10.11.25.138:4000/v1/graphql'; l_buffer varchar2(32767); l_item varchar2(1024); l_queue varchar2(32) := 'wss.notification.demo'; l_wssid varchar2(36) := '00013cf5-719b-401d-9ae2-dcf2da5ee4fc'; l_query varchar2(32767); procedure send(empno_in in number) is rec emp%rowtype; l_message varchar2(1024); begin select * into rec from emp where empno = empno_in; l_message := '"{'|| '\"wssid\":' ||'\"'||l_wssid||'\",'|| '\"id\":' ||'\"'||rec.empno||'-'||rec.deptno||'-'||rec.job||'\",'|| '\"title\":' ||rec.empno||','|| '\"message\":' ||'\"'||rec.ename||'\",'|| '\"created\":'||'\"'||to_char(sysdate,'yyyy-mm-dd"T"hh24:mi:ss')||'\"'|| '}"'; l_query := '{"query":"mutation sendToQueue($queue: String!, $message: String!)' || ' {\n sendToQueue(queue: $queue, message: $message)\n}",' || '"variables":{"queue":"' || l_queue || '","message":' || l_message ||'}}'; l_http_request := utl_http.begin_request(l_url, 'POST','HTTP/1.1'); utl_http.set_header(l_http_request, 'user-agent', 'mozilla/4.0'); utl_http.set_header(l_http_request, 'content-type', 'application/json; charset=utf-8'); utl_http.set_header(l_http_request, 'Content-Length', lengthb(l_query)); utl_http.set_body_charset(l_http_request, charset => 'utf-8'); utl_http.write_text(l_http_request, l_query); l_http_response := utl_http.get_response(l_http_request); begin loop utl_http.read_line(l_http_response, l_buffer); dbms_output.put_line(l_buffer); end loop ; utl_http.end_response(l_http_response); exception when utl_http.end_of_body then utl_http.end_response(l_http_response); end ; exception when no_data_found then dbms_output.put_line('No data found.'); end ; begin for rec in (select rownum , empno from emp where empno = 7654 ) loop send(rec.empno); end loop ; end ;/
接收與送出所需要的資料欄位請視所需自行調整。
你每次收到的訊息如果有可能都有不同的資料欄位,可修改 Message 組件:
Message 1 2 3 4 5 6 7 8 9 const Message = ( props ) => { return ( <p> {Object .keys(props).map((prop, i ) => (<span key ={i} > { prop.toUpperCase() }: { props[prop] } </span > ) )} </p> ); };