闽公网安备 35020302035485号
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 两种状态都由后端提供并管理。这使得前端拥有一个状态,但实际上允许在后端管理该状态,是一种有趣的结合。