我大约在三年前开始在工作中使用 react。巧合的是,当时正好是 React Hooks 出来的时候。我当时的项目代码库有很多类组件,总让我觉得很笨重。
我们来看看下面的例子:一个每秒递增一次的计数器。
class Counter extends React.Component {
constructor() {
super();
this.state = { count: 0 };
this.increment = this.increment.bind(this);
}
increment() {
this.setState({ count: this.state.count + 1 });
}
componentDidMount() {
setInterval(() => {
this.increment();
}, 1000);
}
render() {
return <div>The count is: {this.state.count}</div>;
}
}
对于一个自动递增的计数器来说要写这么多代码可不算少。更多的模板和仪式意味着出错的可能性更大,开发体验也更差。
Hooks 很漂亮,但是容易出错
当 hooks 出现的时候我非常兴奋。我的计数器可以简化为以下写法:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
setInterval(() => {
setCount(count + 1);
}, 1000);
}, []);
return <div>The count is: {count}</div>;
}
等等,这其实是不对的。我们的 useEffect hook 在 count 周围有一个陈旧闭包,因为我们没有把 count 包含在 useEffect 依赖数组中。从依赖数组中省略变量是 React hooks 的一个常见错误,如果你忘记了,有一些 linting 规则会警告你的。
我稍后会回到这个问题上。现在,我们把缺少的 count 变量添加到依赖数组中:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
setInterval(() => {
setCount(count + 1);
}, 1000);
}, [count]);
return <div>The count is: {count}</div>;
}
但现在我们遇到了另一个问题,看看应用程序的运行效果:

精通 React 的人们可能知道发生了什么事情,因为你每天都在与这种问题作斗争:我们创建了太多的间隔(每次重新运行效果时都会创建一个新间隔,也就是每次我们增加 count 时间隔都会增加)。可以通过几种方式来解决这个问题:
从清除间隔的 useEffect hook 返回一个清理函数
使用 setTimeout 代替 setInterval(还是要使用清理函数)
使用 setCount 的函数形式来避免直接引用当前值
事实上哪种办法都行得通。我们在这里实现最后一个选项:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
setInterval(() => {
setCount((count) => count + 1);
}, 1000);
}, []);
return <div>The count is: {count}</div>;
}
我们的计数器修好了!由于依赖数组中没有任何内容,因此我们只创建了一个间隔。由于我们为计数设置器使用了回调函数,因此永远不会在 count 变量上有陈旧闭包。
这是一个人为做出来的例子,但除非你已经使用 React 一段时间,否则它仍然很令人困惑。我们中有许多人每天都会遇到更复杂的情况,即使是最有经验的 React 开发人员也会为之头痛不已。
假的响应性
我思考了很多关于 hooks 的事情,想知道为什么它们感觉不太对劲。结果我通过探索 Solid.js 找到了答案。
React hooks 的问题在于 React 并不是真正的响应式设计。如果 linter 知道一个效果(或回调或 memo)hook 何时缺少依赖项,那么为什么框架不能自动检测依赖项并对这些更改做出响应呢?
深入研究 Solid.js
关于 Solid,首先要注意的是它没有尝试重新发明轮子:它看起来很像 React,因为 React 有一些显眼的模式:单向、自上而下的状态;JSX;组件驱动的架构。
如果我们用 Solid 重写 Counter 组件,会这样开始:
function Counter() {
const [count, setCount] = createSignal(0);
return <div>The count is: {count()}</div>;
}
到目前为止我们看到了一个很大的不同点:count 是一个函数。这称为访问器(accessor),它是 Solid 工作机制的重要组成部分。当然,我们这里没有关于按间隔递增 count 的内容,所以下面把它添加进去:
function Counter() {
const [count, setCount] = createSignal(0);
setInterval(() => {
setCount(count() + 1);
}, 1000);
return <div>The count is: {count()}</div>;
}
这肯定行不通,对吧?每次组件渲染时不会设置新的间隔吗?
没有。它就这么正常运行了。
但为什么会这样?好吧,事实证明 Solid 不需要重新运行 Counter 函数来重渲染新的计数。事实上,它根本不需要重新运行 Counter 函数。如果我们在 Counter 函数中添加一个 console.log 语句,就会看到它只运行一次。
function Counter() {
const [count, setCount] = createSignal(0);
setInterval(() => {
setCount(count() + 1);
}, 1000);
console.log('The Counter function was called!');
return <div>The count is: {count()}</div>;
}
在我们的控制台中,只有一个孤独的日志语句:
"The Counter function was called!""The Counter function was called!"在 Solid 中,除非我们明确要求,否则代码不会多次运行。
但是 hooks 呢?
于是我在 Solid 中解决了 React useEffect hook 的问题,而无需编写看起来像 hooks 的东西。我们可以扩展我们的计数器例子来探索 Solid 效果。
如果我们想在每次计数增加时 console.log count 怎么办?你的第一反应可能是在我们的函数中使用 console.log:
function Counter() {
const [count, setCount] = createSignal(0);
setInterval(() => {
setCount(count() + 1);
}, 1000);
console.log(`The count is ${count()}`);
return <div>The count is: {count()}</div>;
}
但这不起作用。请记住,Counter 函数只运行一次!但我们可以使用 Solid 的 createEffect 函数来获得想要的效果:
function Counter() {
const [count, setCount] = createSignal(0);
setInterval(() => {
setCount(count() + 1);
}, 1000);
createEffect(() => {
console.log(`The count is ${count()}`);
});
return <div>The count is: {count()}</div>;
}
这行得通!而且我们甚至不必告诉 Solid,说这个效果取决于 count 变量。这才是真正的响应式设计。如果在 createEffect 函数内部调用了第二个访问器,它也会让效果运行起来。
一些更有趣的 Solid 概念
响应性,而不是生命周期 hooks
如果你已经在 React 领域有一段时间的经验了,那么下面的代码更改可能真的会让你大跌眼镜:
const [count, setCount] = createSignal(0);
setInterval(() => {
setCount(count() + 1);
}, 1000);
createEffect(() => {
console.log(`The count is ${count()}`);
});
function Counter() {
return <div>The count is: {count()}</div>;
}
并且代码仍然是有效的。我们的 count 信号不需要存在于一个组件函数中,依赖它的效果也不需要。一切都只是响应式系统的一部分,“生命周期 hooks”实际上并没有起到太大的作用。
细粒度的 dom 更新
前面我主要关注的是 Solid 的开发体验(例如更容易编写没有错误的代码),但 Solid 的性能表现也得到了很多赞誉。其强大性能的一个关键来源是它直接与 DOM 交互(无虚拟 DOM)并执行“细粒度”的 DOM 更新。
考虑对我们的计数器进行以下调整:
function Counter() {
const [count, setCount] = createSignal(0);
setInterval(() => {
setCount(count() + 1);
}, 1000);
return (
<div>
The {(console.log('DOM update A'), false)} count is:{' '}
{(console.log('DOM update B'), count())}
</div>
);
}
运行它会在控制台中获得以下日志:
DOM update A
DOM update B
DOM update B
DOM update B
DOM update B
DOM update B
DOM update B
换句话说,每秒更新的唯一内容是包含 count 的一小部分 DOM。Solid 甚至没有重新运行同一 div 中较早的 console.log。
小 结
在过去的几年里我很喜欢使用 React;在处理实际的 DOM 时,我总感觉它有着正确的抽象级别。话虽如此,我也开始注意到 React hooks 代码经常变得容易出错。我感觉 Solid.js 使用了 React 的许多符合人体工程学的部分,同时最大程度减少了混乱和错误。本文向你展示的是 Solid 的一些让我惊叹的部分,感兴趣的话我建议你查看 https://www.solidjs.com 并自己探索这个框架。
原文链接:https://typeofnan.dev/solid-js-feels-like-what-i-always-wanted-react-to-be/
本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!
如今的 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 )。这样就可以确保在任何时间总是拿到正确的实例
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!