2.服务端状态 Server State:客户端通过异步请求获得的数据。
function Text() { const [viewMore, setViewMore] = useState(false); return ( <Fragment> <p> React makes it painless to create interactive UIs. { viewMore && <span> Design simple views for each state in your application. </span> } </p> <button onClick={() => setViewMore(true)}>read more</button> </Fragment> ) }viewMore 是一种仅对这个特定组件有意义的状态,它的作用是仅控制此处文本的可见性。在此示例中, viewMore 对应用程序的其他组件对都是没用的,因此,您不必将此状态泄漏到 Text 组件之外。
import { useState } from "react"; const Skill = ({ onChange }) => ( <label> 技能: <input type="text" onChange={(e) => onChange(e.target.value)} /> </label> ); const Years = ({ onChange }) => ( <label> 工龄: <input type="text" onChange={(e) => onChange(e.target.value)} /> </label> ); export default function Form() { const [skill, setSkill] = useState(""); const [years, setYears] = useState(""); const isFormReady = skill !== "" && years !== ""; return ( <form onSubmit={() => alert("提及成功")}> <Skill onChange={setSkill} /> <br /> <Years onChange={setYears} /> <button disabled={!isFormReady}>submit</button> </form> ); }这里我们有一个 Form 表单,它包含两个字段 skill 和 years ,默认情况下,Form 表单的提交按钮处于禁用状态,仅当两个输入都有值时该按钮才变为启用状态,请注意 skill 和 years 都是必需的,以便我们可以计算 isFormReady 的值。Form 是实现此类逻辑的最佳场所,因为它包含了所有相关联的元素。
import React, { useContext, useState } from "react"; const ThemeContext = React.createContext(); const Theme = ({ onChange }) => { const { theme } = useContext(ThemeContext); return `Theme: ${theme}`; } const ThemeSelector = () => { const { theme, toggleTheme } = useContext(ThemeContext); return ( <select value={theme} onChange={toggleTheme}> <option value="light">light</option> <option value="dark">dark</option> </select> ); } export default function App() { const [theme, setTheme] = useState("light"); const toggleTheme = () => setTheme(theme === "light" ? "dark" : "light"); const themeStyle = { background: theme === "light" ? "#fff" : "#b9b9b9" }; return ( <ThemeContext.Provider value={{ theme, toggleTheme }}> <div className="App"> <header style={themeStyle}> <Theme /> </header> <footer style={themeStyle}> <ThemeSelector /> </footer> </div> </ThemeContext.Provider> ); }这个例子中,有一个页头和一个页脚,他们都需要知道当前应用程序的主题。我们通过使用 Context API 将 theme 设置为应用状态,这样做的目的以便于 Theme 和 ThemeSelector 也能轻松的访问(它们也需要访问 theme,但它们有可能嵌套于其他组件之中)。如果某些属性许多组件都需要,并且可能需要从远程组件进行更新,那么我们可能必须将其设置为应用程序状态。
const initialState = {count: 0}; // 堆代码 duidaima.com function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); }状态提升 - Lifting State Up
// viewMore 是一个 local state function Component() { const [viewMore, setViewMore] = useState(false); return ( <Fragment> <p>Text... { viewMore && <span>More text ...</span>}</p> <button onClick={() => setViewMore(true)}>read more</button> </Fragment> ); } // 将 viewMore 提升为组件的 feature state function Component({ viewMore }) { return ( <p>Text... { viewMore && <span>More text ...</span>}</p> ); }**当您看到一个局部状态变量变成一个 props 时,我们就可以视其为状态提升。**这种方式的需要注意在 prop drilling 方面的找到平衡,您也不希望有许多只是负责将 props 传递给他们子组件的「中间人」组件。
function SplitPane(props) { return ( <div className="SplitPane"> <div className="SplitPane-left"> {props.left} </div> <div className="SplitPane-right"> {props.right} </div> </div> ); } function App() { return ( <SplitPane left={ <Contacts /> } right={ <Chat /> } /> ); }示例中没有使用 children,而是自行约定将 Contacts 和 Chat 两个组件通过 props.left/props.right 传入,被传递的组件同父组件组成了更加复杂的组件,虽然被组合在一起,但各个成员组件都具有单一职责。
// Apollo import { useQuery, gql } from '@apollo/client'; // 在这个例子中,我们使用了 GraphQL const EXCHANGE_RATES = gql` query GetExchangeRates { rates(currency: "USD") { currency rate } } `; function ExchangeRates() { const { loading, error, data } = useQuery(EXCHANGE_RATES); if (loading) return <p>Loading...</p>; if (error) return <p>Error :(</p>; return data.rates.map(({ currency, rate }) => ( <div key={currency}> <p> {currency}: {rate} </p> </div> )); } // ReactQuery import React from "react"; import ReactDOM from "react-dom"; import { QueryClient, QueryClientProvider, useQuery } from "react-query"; const queryClient = new QueryClient(); export default function App() { return ( <QueryClientProvider client={queryClient}> <Example /> </QueryClientProvider> ); } function Example() { const { isLoading, error, data, isFetching } = useQuery("repoData", () => fetch( "https://api.github.com/repos/tannerlinsley/react-query" ).then((res) => res.json()) ); if (isLoading) return "Loading..."; if (error) return "An error has occurred: " + error.message; return ( <div> <h1>{data.name}</h1> <p>{data.description}</p> <strong>👀 {data.subscribers_count}</strong>{" "} <strong>✨ {data.stargazers_count}</strong>{" "} <strong>🍴 {data.forks_count}</strong> <div>{isFetching ? "Updating..." : ""}</div> </div> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);从例子中我们发现,前端负责指定如何获取数据,其余的都取决于 Apollo/ReactQuery 客户端,包括前端需要的 loading 和一个 error 两种状态都由后端提供并管理。这使得前端拥有一个状态,但实际上允许在后端管理该状态,是一种有趣的结合。