在线呼叫网页版,seo免费视频教程,网上哪里可以定制衣服,如何制作学校网站Effect的概念起源
从输入输出的角度理解Effect https://link.excalidraw.com/p/readonly/KXAy7d2DlnkM8X1yps6L
编程中的Effect起源于函数式编程中纯函数的概念
纯函数是指在相同的输入下#xff0c;总是产生相同的输出#xff0c;并且没有任何副作用(side effect)的函数。…Effect的概念起源
从输入输出的角度理解Effect https://link.excalidraw.com/p/readonly/KXAy7d2DlnkM8X1yps6L
编程中的Effect起源于函数式编程中纯函数的概念
纯函数是指在相同的输入下总是产生相同的输出并且没有任何副作用(side effect)的函数。
副作用是指函数执行过程中对函数外部环境进行的可观察的改变比如修改全局变量、打印输出、写入文件等。 前端的典型副作用场景是 浏览器环境中在window上注册变量 副作用引入了不确定性使得程序的行为难以预测和调试。为了处理那些需要进行副作用的操作函数式编程引入了Effect的抽象概念。
它可以表示诸如读取文件、写入数据库、发送网络请求、DOM渲染等对外部环境产生可观察改变的操作。通过将这些操作包装在Effect中函数式编程可以更好地控制和管理副作用使得代码更具可预测性和可维护性。
实际工作中我们也是从React的useEffect开始直接使用Effect的说法
React: useEffect
useEffect is a React Hook that lets you synchronize a component with an external system.
import { useState, useEffect } from react;
// 模拟异步事件
function getMsg() {return new Promise((resolve) {setTimeout(() {resolve(React)}, 1000)})
}export default function Hello() {const [msg, setMsg] useState(World)useEffect(() {getMsg().then((msg) {setMsg(msg)})const timer setInterval(() {console.log(test interval)})return () {// 清除异步事件clearTimeout(timer)}}, [])return (h1Hello { msg }/h1);
}
Effect中处理异步事件并在此处消除异步事件的副作用clearTimeout(timer)避免闭包一直无法被销毁
Vue: watcher
运行期自动依赖收集 示例
script setup
import { ref } from vue
const msg ref(World!)setTimeout(() {msg.value Vue
}, 1000)
/scripttemplateh1Hello {{ msg }}/h1
/template
_createElementVNode(h1, null, _toDisplayString(msg.value), 1 /* TEXT */)
runtime的render期间通过msg.value对msg产生了引用此时产生了一个watch effectmsg的watchlist中多了一个render的watcher在msg变化的时候 render会通过watcher重新执行
Svelte: $
编译器依赖收集 示例
suffix的值依赖name在name变化之后suffix值也更新
scriptlet name world;$: suffix name !setTimeout(() {name svelte}, 1000)
/scripth1Hello {suffix}/h1
// 编译后部分代码
function instance($$self, $$props, $$invalidate) {let suffixlet name worldsetTimeout(() {$$invalidate(1, (name svelte))}, 1000)// 更新关系$$self.$$.update () {if ($$self.$$.dirty /*name*/ 2) {$: $$invalidate(0, (suffix name !))}}return [suffix, name]
}
Effect分类
React先介绍了两种典型的Effect
渲染逻辑中可以获取 props 和 state并对它们进行转换然后返回您希望在屏幕上看到的 JSX。渲染代码必须是纯的就像数学公式一样它只应该计算结果而不做其他任何事情。事件处理程序是嵌套在组件内部的函数它们执行操作而不仅仅做计算。事件处理程序可以更新输入字段、提交HTTP POST请求以购买产品或将用户导航到另一个页面。它包含由用户特定操作例如按钮点击或输入引起的 “副作用”它们改变程序的状态。
Consider a ChatRoom component that must connect to the chat server whenever it’s visible on the screen. Connecting to a server is not a pure calculation (it’s a side effect) so it can’t happen during rendering. However, there is no single particular event like a click that causes ChatRoom to be displayed.
考虑一个ChatRoom组件每当它在屏幕上可见时都必须连接到聊天服务器。连接到服务器不是一个纯粹的计算它是一个副作用因此不能在渲染期间发生渲染必须是纯函数。然而并没有单个特定的事件如点击会触发ChatRoom的展示
Effects let you specify side effects that are caused by rendering itself, rather than by a particular event. Sending a message in the chat is an event because it is directly caused by the user clicking a specific button. However, setting up a server connection is an Effect because it should happen no matter which interaction caused the component to appear. Effects run at the end of a commit after the screen updates. This is a good time to synchronize the React components with some external system (like network or a third-party library).
Effect 允许指定由渲染本身引起的副作用而不是由特定事件引起的副作用。在聊天中发送消息是一个事件因为它直接由用户点击特定按钮引起。然而不管是任何交互触发的组件展示_设置服务器连接_都是一个Effect。Effect会在页面更新后的commit结束时运行。这是与某个外部系统如网络或第三方库同步React组件的好时机 以下Effect尽量达到不重不漏不重的意义是他们之间是相互独立的每个模块可以独立实现这样可以在系统设计的初期可以将业务Model建设和Effect处理分离甚至于将Effects提取成独立的utils 渲染
生命周期
组件被初始化、更新、卸载的时候我们需要做一些业务逻辑处理例如组件初始化时调用接口更新数据
React
react基于自己的fiber结构通过闭包完成状态的管理不会建立值和渲染过程的绑定关系通过在commit之后执行Effect达到值的状态更新等副作用操作因此声明周期需要自己模拟实现
import { useState, useEffect } from react;export default function Hello() {const [msg, setMsg] useState(World)// dependency是空 因此只会在第一次执行 声明周期上可以理解为onMounteduseEffect(() {// 异步事件const timer setTimeout(() {// setMsg会触发重渲染 https://react.dev/learn/render-and-commitsetMsg(React)}, 1000)return () {// 卸载时/重新执行Effect前 清除异步事件clearTimeout(timer)}// 如果dependency有值 则每次更新如果dependency不一样就会执行Effect}, [])return (h1Hello { msg }/h1);
}
script setup
import { onMounted, onUnmounted, onUpdated, ref } from vueconst msg ref(Hello World!)
// 挂载
onMounted(async () {function getValue() {return Promise.resolve(hello, vue)}const value await getValue()msg.value value
})
onUpdated(() {}) // 更新
onUnmounted(() {}) // 卸载
/scripttemplateh1{{ msg }}/h1input v-modelmsg
/template
scriptimport { onMount, onDestroy, beforeUpdate } from sveltelet name world$: suffix name !onMount(() {setTimeout(() {name svelte}, 1000)})beforeUpdate(() {}) // 更新onDestroy(() {}) // 卸载/销毁
/scripth1Hello {suffix}/h1
Action 用户行为
对应React中提到的两个典型Effect中的 事件处理程序
在不考虑跳出应用location.hrefxxx的情况下我们的行为都只能改变当前应用的状态不管是输入、选择还是触发异步事件的提交网络相关的副作用在下节讨论
点击/输入
!-- 原生 要求onClick是全局变量 --
div onclickonClick/
!-- React --
div onClick{onClick}/
!-- Vue --
div clickonClick/
!-- Svelte --
div on:clickonClick/
滑动输入、键盘输入等
!-- React view和model的关系需要自己处理 --
input value{value} onChange{val setValue(val)} placeholderenter your name /
!-- Vue 通过指令自动建立view和model的绑定关系 --
input v-modelname placeholderenter your name /
!-- Svelte --
input bind:value{name} placeholderenter your name /
所谓的MVVM即是视图和模型的绑定关系通过框架(v-mode,bind:valuel)完成所以需要自己处理绑定关系的React不是MVVM
滚动
同上
Network 网络请求
基础XMLHttpRequest,Fetch
NPM包AxiosuseSwr
Storage 存储
任何存储行为都是副作用POST请求、变量赋值、local存储、cookie设置、URL参数设置
Remote
缓存/数据库同上 网络请求
Local
内存
局部变量 闭包
React的函数式组件中的useState的值的变更
全局变量 window
浏览器环境初始化完成之后我们的context中就会有window全局变量修改window的属性会使同一个页面环境中的所有内容都被影响微前端的window隔离方案除外
LocalStorage
兼容localStorage存储和 原生APP存储返回Promise 其实也可以兼容从接口获取、存储数据
export function getItem(key) {const now Date.now();if (window.XWebView) {window.XWebView.callNative(JDBStoragePlugin,getItem,JSON.stringify({key,}),orange_${now},-1,);} else {setTimeout(() {window[orange_${now}](JSON.stringify({status: 0,data: {result: success,data: localStorage.getItem(key),},}),);}, 0);}return new Promise((resolve, reject) {window[orange_${now}] (result) {try {const obj JSON.parse(result);const { status, data } obj;if (status 0 data data.result success) {resolve(data.data);} else {reject(result);}} catch (e) {reject(e);}window[orange_${now}] undefined;};});
}export function setItem(key, value BABEL_CHANNEL) {const now Date.now();if (window.XWebView) {window.XWebView.callNative(JDBStoragePlugin,setItem,JSON.stringify({key,value,}),orange_${now},-1,);} else {setTimeout(() {window[orange_${now}](JSON.stringify({status: 0,data: {result: success,data: localStorage.setItem(key, value),},}),);}, 0);}return new Promise((resolve, reject) {window[orange_${now}] (result) {console.log(MKT ~ file: storage.js:46 ~ returnnewPromise ~ result:, result);try {const obj JSON.parse(result);const { status, data } obj;if (status 0 data data.result success) {resolve(data.data);} else {reject(result);}} catch (e) {reject(e);}window[orange_${now}] undefined;};});
}
Cookie
https://www.npmjs.com/package/js-cookie URL
参见地址栏参数
举个栗子
组件诉求 支持分页 支持搜索 已选择的门店需要回显但是已选择的门店只能分页获取无法全部获取 需要知道用户移除了哪些选项增加了哪些选项 支持服务端全选
组件Effect分析
业务组件可以视load-data为纯函数因为loda-data的调用不会影响外部业务组件清晰的Effects归属可以降低业务的复杂度最大程度上降低组件的耦合用户在组件内的行为除了确定之外产生的Effect只对组件自身产生影响提升了组件的内聚 组件模型设计
组件list兼容搜索和下拉场景
const { result: list, hasNext } await this.loadData(param).catch(() ({ hasNext: false, result: [] }))
const lastRemove this.remove // 本次新增之前移除的内容
if (param.pageNo 1 !param.search) {this.list list
} else {// 建立新值的索引 接口返回的信息是无状态属性的选中与否const map list.reduce((pre, cur) {pre[cur.id] Object.assign(cur, { from: param.search })return pre}, {})// 此处应该遍历list 而不是 this.listthis.list this.list.map(item {const diff map[item.id]// 找到之前已经有的数据 就从map中移动到之前list的位置做替换if (diff) delete map[item.id]return diff || item// 剩余的值补充到最后面}).concat(Object.values(map))
}
const value diffBy(this.last.add.concat(this.remote, this.local, this.checked), lastRemove)
this.value value 接口返回选中的值通过checked-by-remote纯函数的依赖反转实现惰性计算业务组件默认选中的值通过checked-by-local纯函数的依赖反转实现惰性计算增加或者移除的值通过相应的diff计算出来Reactivity极大提升了Model的表达能力
{computed: {/*** 接口返回已选中的数据且不能在已移除的数据中, 否则上次移除的数据会被自动选中*/remote() {return diffBy(this.list.filter(this.checkedByRemote || emptyFilter).map(it it.id), this.last.remove)},/*** 本地默认选中 且不是从remote选中的 且不是上次选中的*/local() {return diffBy(this.list.filter(this.checkedByLocal || emptyFilter).map(it it.id), this.remote, this.last.add)},// 用户选择的checked() {return diffBy(this.value, this.remote, this.last.add, this.local)},// 1. 本地有接口没有的 是新增this.value中已包含了last.add 2. 需要新增的且不在上次本地移除的范围内上次移除的可能不在this.remote范围内add() {return diffBy(this.value, this.remote, this.last.remove)},// 1. 接口有本地没有的 是移除 2. 需要移除的 且 不在上次本地新增的范围内remove() {return this.last.remove.concat(diffBy(this.remote, this.value, this.last.remove))}},
}
参考资料
面向 Model 编程的前端架构设计 https://mp.weixin.qq.com/s/g4hnfirDmyeuXAdEt-zk9wSynchronizing with Effects https://react.dev/learn/synchronizing-with-effects 作者京东零售 刘威 来源京东云开发者社区 转载请注明来源