react 是一个视图层框架,其核心思想是 UI = f(state),即「UI 是 state 的投影」,state 自上而下流动,整个 React 组件树由 state 驱动。当一个 React 应用程序足够复杂,组件嵌套足够深时,组件树中的状态流动会变得难以控制(例如你如何跟踪父节点的 state 流动到叶子节点时产生的变化)。这时我们就需要对 state 进行管理,在进行状态管理的同时,还需要分清 React 应用中有哪些状态类型,方便制定出最适合的状态管理方案。
React 应用程序状态从宏观意义上讲可以分为两类:
仅存在于单个组件中的状态,我们可以称之为「本地」或「UI」状态。
通常它可以帮助我们管理用户界面交互,例如显示和隐藏内容或启用和禁用按钮,它还经常在我们等待时控制渲染的内容。考虑以下示例:
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 组件之外。
组合两个或多个组件,这些组件需要知道相同的信息,我们将这种状态定义为「特征」状态。
可以说,每一个非本地性的状态都属于这一类。然而,并非每个特征状态都是相同的,我们将在本文中进一步了解这一点。
特征状态的一个很好的例子是表单状态,它看起来有点像上面描述的 UI 状态,但它结合了多个输入,管理多个组件。
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 是实现此类逻辑的最佳场所,因为它包含了所有相关联的元素。
此示例体现了特征状态和应用状态之间的一个界限,在这里也可以使用 Redux 进行状态管理,但是这不是一种好的做法,我们应该在它被提升并成为应用程序状态之前更早地识别特征状态。
应用程序状态是引导用户整体体验的状态,这可能是授权状态、配置文件数据或全局样式主题。在应用程序的任何地方都可能需要它。下面是一个简单的示例:
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,但它们有可能嵌套于其他组件之中)。
如果某些属性许多组件都需要,并且可能需要从远程组件进行更新,那么我们可能必须将其设置为应用程序状态。
服务器状态可以理解为接口状态,服务端状态有以下特点:存储在远端,本地无法直接控制、需要异步 API 来查询和更新、可能在不知情的情况下,被另一个请求方更改了数据,导致数据不同步等。
现有的状态管理库(如 Mobx、Redux 等)适用于管理客户端状态,但它们并不关心客户端是如何异步请求远端数据的,所以他们并不适合处理异步的、来自服务端的状态。
要识别服务器状态,您必须考虑数据更改的频率以及这些数据的来源。如果它或多或少是静态的,那么我们应该避免从服务端获取,只需将其存储到客户端并将其作为全局变量传递。
随着 React 生态不断壮大,社区中提供了多种方式来解决状态管理,有基于 Flux 思想的 Redux、Zustand 以及 React 自带的 useReducer + Context;有基于原子化的 Recoil、Jotail 等;还有响应式方案的代表 Mobx,在这里不过多针对介绍,我们来了解一些最常用的。
useState 或 useReducer 是大多数人用来管理本地状态的方式,useState 实际上是 React 中重新渲染的主要机制,这也是为什么大多数状态管理库都在底层使用它的原因。如果深入研究,你会发现这些不同的第三方库提供的 Custom Hooks 都依赖于默认的 useState、useReducer 和 useffect,下面是官网提供的一个 useReducer 的简单示例:
const initialState = {count: 0};
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>
</>
);
}
在 React 中,将多个组件中需要共享的 state 向上移动到它们的最近共同父组件中,便可实现共享 state,这就是所谓的「状态提升」。
回顾一下本文开头的 view-more 示例(本地状态来控制文本的可见性)。但是,如果有一个新的要求,我们有一个全局的「展开文本」按钮,然后我们必须把这个状态提升,然后通过 props 传递下去。
// 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 传递给他们子组件的「中间人」组件。
Context API 提供了一种通过组件树传递数据的方法,而无需在每个级别手动向下传递 props。
在典型的 React 应用程序中,数据通过 props 自上而下(父级到子级)传递,但这种做法对于某些类型的 props 而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。
Context 设计目的是为了共享那些对于一个组件树而言是「全局」的数据,例如当前认证的用户、主题或首选语言,因此 Context 主要应用于应用状态的管理(见应用状态示例)。
如果你只是想避免层层传递一些属性,组件组合(component composition)有时候是一个比 context 更好的解决方案。
Context API 适合管理应用状态,如果想避免 Props Drilling 等问题,可以采用组合模式。组合是一种通过将各组件联合在一起以创建更大组件的方式,组合不仅具有多变的灵活性和可重用性,还具有单一职责的特性,组合也是 React 的核心。 看一个官网的示例:
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 和 ReactQuery 中,基本上都是在应用程序中添加一个 layer/client 并由其负责从外部源请求/缓存数据。
它们带有一些不错的 hooks,对于客户端来说,这种方式看起来很像使用本地状态,它消除了数据存储和获取的繁杂性。下面是 Apollo 和 ReactQuery 的简单示例:
// 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 两种状态都由后端提供并管理。这使得前端拥有一个状态,但实际上允许在后端管理该状态,是一种有趣的结合。
状态管理很复杂,状态管理没有最好的方案,只有最合适的方案。
来自:https://segmentfault.com/a/1190000041119092
如今的 Web 前端已被 React、Vue 和 Angular 三分天下,尽管现在的 jQuery 已不再那么流行,但 jQuery 的设计思想还是非常值得致敬和学习的,特别是 jQuery 的插件化。
受控组件与非受控组件在官网与国内网上的资料都不多,有些人觉得它可有可不有,也不在意。这恰恰显示React的威力,满足不同规模大小的工程需求。
一般在传统模式下,我们构建前端项目很简单。就是下载各种js文件,如JQuery、Echart等,直接放置在html静态文件。Webpack则是JavaScript中比较知名的打包工具。这两个构建工具构成了React应用快速搭建的基础。
Gatsby能快速的使用 React 生态系统来生成静态网站,可以结合React Component、Markdown 和服务端渲染来完成静态网站生成让他更强大。
React推出后,出于不同的原因先后出现三种定义react组件的方式,殊途同归;具体的三种方式:函数式定义的无状态组件、es5原生方式React.createClass定义的组件、es6形式的extends React.Component定义的组件
React主要思想是通过构建可复用组件来构建用户界面,每个组件都有自己的生命周期,它规定了组件的状态和方法需要在哪个阶段改变和执行。所谓组件就是有限状态机,,表示有限个状态以及在这些状态之间的转移和动作行为的模型。
React 相关的优化:使用 babel-react-optimize 对 React 代码进行优化,检查没有使用的库,去除 import 引用,按需打包所用的类库,比如 lodash 、echarts 等.Webpack 构建打包存在的问题两个方面:构建速度慢,打包后的文件体积过大
这篇文章主要介绍React Router定义路由之后如何传值,有关React和React Router 。react router中页面传值的三种方法:props.params、query、state
react 高阶组件简单的理解是:一个包装了另一个基础组件的组件。高阶组件的两种形式:属性代理(Props Proxy)、反向继承 (Inheritance Inversion)
React 支持一种非常特殊的属性 Ref ,你可以用来绑定到 render() 输出的任何组件上。这个特殊的属性允许你引用 render() 返回的相应的支撑实例( backing instance )。这样就可以确保在任何时间总是拿到正确的实例
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!