如何将React自定义hook useData改造为Context实现跨组件共享运行时数据
Context改造方案
前置修复:修正原useData的runtime计算逻辑
原代码中runtime是同步计算的,而异步请求fetch的执行时间不会被统计到,所以先调整useData的逻辑:
// 修改hooks/useData.js的fetchData和useEffect部分 const fetchData = async (tableName) => { // 改为async函数 setData([]); const name = TABLE_NAMES.find((name) => name === tableName); if (name) { setError(false); const res = await fetch(getURL(tableName)); const data = await res.text(); await convertToJson(data); } else { setError(true); toast.error("Please enter a valid query"); } }; useEffect(() => { if (!tableName) return; // 空query不执行请求 const t0 = performance.now(); const runQuery = async () => { await fetchData(tableName); const t1 = performance.now(); setRuntime(t1 - t0); console.log("请求耗时:" + (t1 - t0) + " 毫秒"); }; runQuery(); }, [tableName]);
步骤1:创建DataContext
新建src/contexts/DataContext.js文件:
import React, { createContext, useContext } from 'react'; import useData from '../hooks/useData'; const DataContext = createContext(undefined); // 提供器组件 export const DataProvider = ({ children, tableName }) => { const dataRes = useData(tableName); return ( <DataContext.Provider value={dataRes}> {children} </DataContext.Provider> ); }; // 自定义取值hook,避免重复写useContext export const useDataContext = () => { const context = useContext(DataContext); if (!context) throw new Error('useDataContext必须在DataProvider内部使用'); return context; };
步骤2:改造App.js接入Provider
将需要共享数据的Editor、TableSection组件用DataProvider包裹:
import React, { useState } from "react"; import "./assets/output.css"; import Footer from "./components/layouts/Footer"; import Navbar from "./components/layouts/Navbar"; import Sidebar from "./components/layouts/Sidebar"; import TableSection from "./components/table/TableSection"; import Editor from "./components/editor/Editor"; import { DataProvider } from "./contexts/DataContext"; // 引入Provider const App = () => { const [query, setQuery] = useState(""); const [value, setValue] = useState("select * from customers"); return ( <> <div className="grid grid-cols-layout-desktop grid-rows-layout-desktop bg-gray-600 h-screen"> <Navbar /> <Sidebar setQuery={setQuery} setValue={setValue} /> {/* 把query作为tableName传给Provider */} <DataProvider tableName={query}> <Editor setQuery={setQuery} value={value} setValue={setValue} /> {query ? <TableSection /> : null} </DataProvider> <Footer /> </div> </> ); }; export default App;
步骤3:改造Editor组件获取runtime
Editor组件直接从Context取runtime,不需要自己调用useData,不会因为输入重渲染触发请求:
import React from "react"; import AceEditor from "react-ace"; import "ace-builds/src-min-noconflict/ext-language_tools"; import "ace-builds/src-min-noconflict/mode-mysql"; import "ace-builds/src-noconflict/theme-github"; import { useDataContext } from "../../contexts/DataContext"; // 引入Context hook const Editor = ({ setQuery, value, setValue }) => { const { runtime } = useDataContext(); // 从Context取runtime const onChange = (newValue) => { setValue(newValue); }; const onSubmit = () => { var Z = value.toLowerCase().slice(value.indexOf("from") + "from".length); setQuery(Z.split(" ")[1]); }; // 可以在任意位置渲染runtime,比如按钮旁边 return ( <div className="col-start-2 col-end-3 row-start-2 row-end-3 m-6"> <AceEditor aria-label="query editor input" mode="mysql" theme="github" name={Math.floor(Math.random() * 100000).toString()} fontSize={16} minLines={15} maxLines={10} width="100%" showPrintMargin={false} showGutter placeholder="Write your Query here..." editorProps={{ $blockScrolling: true }} setOptions={{ enableBasicAutocompletion: true, enableLiveAutocompletion: true, enableSnippets: true, }} value={value} onChange={onChange} showLineNumbers /> <div className=""> <button className="bg-white text-gray-800 rounded-md font-semibold px-4 py-2 my-4 mr-4" onClick={onSubmit} > <i className="fas fa-play"></i> Run SQL </button> {runtime && <span className="text-white">本次查询耗时:{runtime.toFixed(2)}ms</span>} </div> </div> ); }; export default Editor;
步骤4:改造TableSection组件取数据
TableSection不需要再接收query参数,也不需要自己调用useData,直接从Context取数据:
import { useDataContext } from "../../contexts/DataContext"; const TableSection = () => { const { data, error } = useDataContext(); // 原有渲染逻辑不变,直接用data、error即可 }
效果说明
只有当用户点击运行按钮修改query值时,才会触发useData的请求逻辑,Editor组件输入时只会修改value状态,不会触发重复请求,同时runtime和data可以在任意被DataProvider包裹的组件中直接获取。
内容的提问来源于stack exchange,提问作者r121
相关产品推荐
相关产品推荐

