umijs / hox Goto Github PK
View Code? Open in Web Editor NEWState sharing for React components.
Home Page: https://hox.js.org
License: MIT License
State sharing for React components.
Home Page: https://hox.js.org
License: MIT License
请问hox和@umijs/plugin-model 功能是不是有点重复?我们想使用简单的数据流取代dva,应该是用hox还是plugin-model呢?麻烦大神给点建议~~
编译ReactNative 遇到
error Unable to resolve module react-dom
from node_modules/hox/create-model.js
: react-dom could not be found within the project.
e.g. we had a custom hook needs fetch data from network.
function someModel() {
let [state, setState] = useState({loading:false, valid:false})
useEffect(()=>{
setState({loading:true, valid:false})
axios().then(()=>{
setState({loading:false, valid:true})
})
}, []);
return state;
}
export default createModel(someModel); //the network request will happens here.
The useEffect() will happen when using 'createModel', what should I do If I want the request happens only when I using that model?
e.g.
import useSomeModel ..
function SomeComponent() {
let model = useSomeModel(); //I want the request happens on the first time using this model
}
具体场景:webSocket的onMessage ,收到消息时。去更新对应的model的state。
可以这样用吗? ws.onmessage = (e) => { useXxxxModel.updateState(e); };
看一下这个例子:https://codesandbox.io/s/hox-best-practice-w96lz
在 combinedInc 与 combinedDec 的区别只在于调用外部与外部的 setState 的顺序不同,却导致触发的 effect 次数不同(inc 1 次;dec 2 次,并其中一次是错误的),这影响就很大了。
以下代码场景:主要实现 语言包加载完成后,在加载主界面,但主界面构建失败(其他组件都使用const {intl} = useLocaleModel()来国际化)。提示可能是出现内部的循环归调用失败..
如直接显示主界面,再切换语言则正常。
暂未上react 的同步模式,目前采用useEffect,setState的方式,来解决加载后刷新的逻辑。
这个方式hox国际化的逻辑 有问题么,还是这种内部订阅方式导致的问题 ??
import React, { useEffect } from 'react';
import useLocalesModel from '@/models/useLocales';
import { useSize } from '@umijs/hooks';
import { Spin } from 'antd';
function Locale({ children }) {
const [body] = useSize(document.querySelector('body'));
const { localeLoaded } = useLocalesModel();
return (
localeLoaded ? { children }
: (
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
alignContent: 'center',
width: body.width,
height: body.height,
margin: 'auto',
textAlign: 'center'
}}
>
<Spin tip="语言加载中..." size="large" />
</div>
)
)
}
export default Locale;
useLocales:
import { useState, useRef, useEffect } from 'react';
import intl from 'react-intl-universal';
import request from 'umi-request';
import { usePersistFn } from '@umijs/hooks';
import _ from 'lodash';
// import scope from 'babel-plugin-console/scope.macro';
// import { useRequest } from '@umijs/hooks;'
import { createModel } from 'hox';
const Locales = [
{
name: '简体中文',
value: 'zh-CN',
icons: '🇨🇳'
},
{
name: 'English',
value: 'en-US',
icons: '🇺🇸'
}
];
function useLocales() {
// 缓存json 防止重复请求
console.log('useLocales running');
const localeList = useRef(new Map());
// 用ref 主要为了避免不必要的刷新。
const curLang = useRef({ lang: null });
const [localeLoaded, setLocaleLoaded] = useState(false);
const [curLocale, setCurLocale] = useState(() => {
const currentLocale = intl.determineLocale({
urlLocaleKey: 'lang',
cookieLocaleKey: 'lang',
localStorageLocaleKey: 'lang'
});
const returnLocale = _.find(Locales, { value: currentLocale }) || Locales[0];
return returnLocale;
});
const loadLocale = (locale) => {
const currentLocale = locale.value;
if (localeList.current.has(currentLocale)) {
intl.init({
currentLocale,
locales: {
[currentLocale]: localeList.current.get(currentLocale)
}
});
curLang.current.lang = currentLocale
} else {
request.get(`public/locales/${currentLocale}.json`, {
responseType: 'json'
})
.then(res => {
intl.init({
currentLocale,
locales: {
[currentLocale]: res
}
});
localeList.current.set(currentLocale, res);
curLang.current.lang = currentLocale;
})
.then(() => {
setCurLocale(locale);
setLocaleLoaded(true);
})
}
};
useEffect(() => {
console.log('effect:', curLocale);
loadLocale(curLocale);
})
const changeCurLocale = usePersistFn((key) => {
if (curLang.current.lang === key) return;
const returnLocale = _.find(Locales, { value: key }) || Locales[0];
loadLocale(returnLocale);
});
return { Locales, curLocale, localeLoaded, changeCurLocale, intl }
}
export default createModel(useLocales);
如果更新状态时,不改变复杂类型对象地址,则不会引起订阅这个hook的其他组件的更新,是这样的吗
const useAccess = () => {
const [access, setAccess] = useState({
test: true,
adminSubmit: false
});
// 已存在则覆盖。
const updateAccess = (newAccess) => {
setAccess(_.merge(access, newAccess)) // 如果不改变复杂类型对象地址,则不会引起引用这个状态的更新
}
return { access, updateAccess }
};
export default createModel(useAccess);
会支持react-native吗, 你们方便增加一些常用的demo吗
如 和nextjs、 和 react-native 、 和 .... 的结合 demo
谢谢
after create hox model and use state value from store fast request seem not working need to refresh to see the view change or sometime can't update state in hox need to refresh. thanks you
When i excute npm i
thow the following exception.
当我执行npm i
的时候会抛出下面的异常,无法解析依赖异常;我想这是因为项目依赖的react版本是17.x,而这个库依赖的版本是16.x,期待作者能够升级react版本到17.x
npm i
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: [email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/react
npm ERR! react@"^17.0.1" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.8.0" from [email protected]
npm ERR! node_modules/hox
npm ERR! hox@"^1.1.2" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR!
npm ERR! See /Users/wanglin/.npm/eresolve-report.txt for a full report.
npm ERR! A complete log of this run can be found in:
现在有一个 model A
const useAModel = usecreateModel(() => {
const [aList] = useState([])
return {
aList
}
})
我想要在 model B 获取到 aList,通过aList的数据生成bList,该怎么做?这是我尝试的方法
const useBModel = usecreateModel(() => {
const { aList } = useAModel()
const [bList] = useState([])
React.useEffect(() => {
// 在这里想参考aList数据,生成一个bList
}, []);
React.useEffect(() => {
// 在这里想订阅aList的更新,对bList进行更新
}, [aList]);
return {
bList
}
})
这种情况useBModel是一个普通的hooks是可以实现的,可是我想用hox将bList存起来,请问使用hox的话该如何做?我尝试上面的办法获得报错是:
The above error occurred in the <Executor> component:
in Executor
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://fb.me/react-error-boundaries to learn more about error boundaries.
谢谢
Hello,这是一个很棒的工具!
不过由于 createModel 将容器组件创建于 root 节点之外,无法与 Context 配合食用
可否在创建 model 时指定挂载的组件?例如
createModel(useXXX, {
station: 'test-station'
})
<Provider>
<HoxStation id="test-station" />
</Provider>
可能会产生异步问题,目前无法与 Context 协同是一个小遗憾
能在非组件中进行更新吗
在一个页面中使用 多个model来修改页面状态,在第一次使用一个方法同时修改两个状态时,浏览器提出警告。
react ,hox 都是最新的版本。
userPageModel.md
userPage.md
loading.md
第一次调用 loadingM.startloading 的方法的时候,会出现浏览器中的警告。
浏览器使用的是chrome,最新版
使用yarn workspace方式时,yarn build的时候缺少库 @types/testing-library__dom
[16:36:03] Using gulpfile ~/../hox/gulpfile.js
[16:36:03] Starting 'build'...
[16:36:03] Starting 'clean-lib'...
[16:36:03] Finished 'clean-lib' after 2.4 ms
[16:36:03] Starting 'copy-files'...
[16:36:03] Finished 'copy-files' after 78 ms
[16:36:03] Starting 'ts'...
/.../node_modules/@types/testing-library__react/index.d.ts(13,49): error TS7016: Could not find a declaration file for module '@testing-library/dom'. '/.../node_modules/@testing-library/dom/dist/index.js' implicitly has an 'any' type.
Try `npm install @types/testing-library__dom` if it exists or add a new declaration (.d.ts) file containing `declare module '@testing-library/dom';`
/.../node_modules/@types/testing-library__react/index.d.ts(16,15): error TS7016: Could not find a declaration file for module '@testing-library/dom'. '/.../node_modules/@testing-library/dom/dist/index.js' implicitly has an 'any' type.
Try `npm install @types/testing-library__dom` if it exists or add a new declaration (.d.ts) file containing `declare module '@testing-library/dom';`
TypeScript: 2 semantic errors
TypeScript: emit succeeded (with errors)
[16:36:10] 'ts' errored after 6.67 s
[16:36:10] Error: TypeScript: Compilation failed
at Output.mightFinish (/.../node_modules/gulp-typescript/release/output.js:130:43)
at applySourceMap.then.appliedSourceMap (/.../node_modules/gulp-typescript/release/output.js:65:22)
[16:36:10] 'build' errored after 6.75 s
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
方案1:
(用户信息的每个字段,分别使用一次useState)
const useUser = () => {
const [id, setId] = useState('');
const [token, setToken] = useState('');
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [points, setPoints] = useState('');
const [avatar, setAvatar] = useState('');
..省略号...
方案2
(把用户信息放在一个对象里,只用了一次useState)
const useUser = () => {
const [user, setuser] = useState(
{
id:'',
token:'',
username:'',
email:'',
points:'',
avatar:'',
}
);
..省略号...
想问问,哪个方案更好, 应该怎么判断自己用哪个方案呢?
如果用第一种,字段太多了, 会不会用hox 会导致性能低下, 浪费很多内容呢?
谢谢
比如 toggle类的model 应当具有普遍性,建议扩展 createModel(customHook,defaultValue) API 已便更灵活的使用
import { createModel } from 'hox';
import { useBoolean } from '@umijs/hooks';
function useToggle(defValue) {
const { state, toggle, setTrue, setFalse } = useBoolean(defValue ?? true);
const set = (bool) => (bool ? setTrue : setFalse)
return { state, toggle, set }
}
export const useSiderMenuToggleModel = createModel(useToggle);
export const useTabsToggleModel = createModel(useToggle);
建议修改API以支持初值设置:
export const useSiderMenuToggleModel = createModel(useToggle,false);
export const useTabsToggleModel = createModel(useToggle,true);
Same as umijs/plugins#65
reproduce repo: hox-react-issue
如果 hox可以 和umi 工作,且支持ssr, 方便给个demo吗
谢谢
为了让开发者调试更方便,可以做一套dev tools
createModel 创建太多的话会不会有影响
withModel
TODO 支持多个 model
参数可以支持数组 和 字符串
TODO 支持重命名 model,this.props.model 如果本来组件就有这个名字了,就有问题了,需要支持重命名。
支持接收函数,函数的参数为所有的 model 数据,类似 react-redux {[key]: modelMap[key].data}
model在应用一启动就会立刻执行。但是我们通常项目里面,后端的接口都是要求要先登录。这样一来,model在应用一启动还没有登录的情况下发请求的话肯定会失败。
所以我的问题是,为什么不把model设计成在某个组件真正使用的时候才触发发请求的动作?
或者,有没有什么办法可以做到在满足某些前置条件的情况下才允许model执行?
componentWillMount = async() => {
const res = await Storage.get("theme")
this.props.themeConfig.changeTheme( res || "light" )
}
当和umi的useRequest一起使用时,我在组件最外层配置了UseAPIProvider的onError属性,当接口500时,不执行onError的回调函数
// useUser.js
const useUser = () => {
const [userInfo, setUser] = useState();
const { run, loading } = useRequest('/api/user', {
manual: true
});
useEffect(() => {
run().then(user => setUser(user));
}, []);
return { userInfo, loading, run };
};
export default createModel(useUser);
// App.js
import { UseAPIProvider } from '@umijs/use-request';
const App = () => {
return (
<UseAPIProvider
value={{
onError: (e) => console.log(e)
}}
>
<Children />
</UseAPIProvider>
)
}
// Children.js
const Chidren = () => {
const user = useUser()
return <h1>Chidren</h1>
}
我直接使用useRequest是可以的捕获到500错误的
照例子写的demo,使用的是本机目录的 hox
./src/counter.js
Module not found: Can't resolve 'hox' in '/.../hox-demo/src'
找到的解决办法: hox 的 package.json 的main 改为 lib/index.js
在 hox v1 中,我们的实现方案是在应用的 React 组件树之外,额外创建一个组件树,以存储 hox 中的 model。然而,这种方案渐渐显露出较多的弊端:
为了解决上述问题,在此 RFC 中,尝试将底层实现改为基于 Context。不过基于 Context 虽然可以解决上述全部问题,但也会存在一些新的弊端:
import {createModel} from 'hox'
function useCounter() {
// ...
}
export const CounterModel = createModel(useCounter)
// 或
export default createModel(useCounter)
import {CounterModel} from './counter.model.ts'
function App() {
return (
<CounterModel.Provider>
<div>
{/* ... */}
</div>
</CounterModel.Provider>
)
}
import {useModel} from 'hox'
import {CounterModel} from './counter.model.ts'
function Foo() {
const counterModel = useModel(CounterModel)
return (
...
)
}
useXxxModel.data
)<CounterModel.Provider ref={yourModelRef}> // 通过 ref 的方式获取
</CounterModel.Provider>
<CounterModel.Provider startFrom={123}> // 通过 ref 的方式获取
</CounterModel.Provider>
interface Props {
startFrom: number
}
const CounterModel = createModel<Props>((props) => {
const [count, setCount] = useState(props.startFrom)
// ...
})
由于存在参数传递,需要给 createModel
增加一个 options.memo
参数来控制何时触发重渲染:
const CounterModel = createModel<Props>((props) => {
const [count, setCount] = useState(props.startFrom)
// ...
}, { // <- options
memo: true // 开启 memo 后,Provider 的重渲染逻辑和普通 React 组件被 memo 后的逻辑类似
})
如果语法较为复杂的话,可以考虑把 memo
的默认值设置为 true
,因为绝大部分场景下都是需要 memo 的。
是叫 model 好还是叫 store 好?
如果取到并调用 useCounterModel.data ( container.data )中 setState,container.data 会改变一次,但不会触发 useModel 中的 setState,导致当前页面不会订阅 state 更新,但实际上 container.data 已经改变,所以比如跳转到有相应 model 的页面时,会拿到已经改变的数据
我的react版本为16.11.0
import { useCounterModel } from "./counter";
这种引入方式会报错,不清楚是否与react版本有关
我看到在提供的CodeSandbox中引入的方式全部为
import useUserModel from "./useUserModel";
是否意味着每一个hox模块都要单独使用一个文件
Uncaught TypeError: Cannot read property 'createElement' of undefined at Object.createModel (create-model.js? [sm]:70) at index.js? [sm]:39 at require (VM1309 WAService.js:19) at VM1309 WAService.js:19 at index.js? [sm]:12 at require (VM1309 WAService.js:19) at VM1309 WAService.js:19 at index.js? [sm]:11 at require (VM1309 WAService.js:19) at <anonymous>:8:7
应该是因为小程序没有createElement这个方法导致的。
修改代码热更新后点击按钮,状态不会更新,不清楚是什么原因
"dependencies": {
"@craco/craco": "^5.7.0",
"hox": "^1.1.2",
"node-sass": "^4.14.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.0",
"simple-progress-webpack-plugin": "^1.1.2",
"web-vitals": "^0.2.4"
},
import { useState } from 'react'
import { createModel } from 'hox'
function useToken() {
const [token, setToken] = useState('');
const changeToken = (payload) => setToken(payload.token);
return { token, changeToken }
}
export default createModel(useToken);
组件中
import React, { memo, useState } from 'react'
import useToken from './usetoken'
export default memo(function Home() {
const token = useToken();
const changeToken = () => token.changeToken({ token: '#123456abc' })
const clearToken = () => token.changeToken({ token: '' })
return (
<div>
<p>token: {token.token}</p>
<button onClick={changeToken}>设置token</button>
<button onClick={clearToken}>清空token</button>
</div>
)
})
在useEffect里使用setModel
直接调用时,外部通过这个hook useModel拿不到更新后的model,貌似没有检测到更新,但是内部model确实变了
使用异步,价格setTimeout之后外部就可以正常拿到更新后的model
示例如下:
import { createModel } from 'hox';
import { useEffect, useState } from 'react';
function useModel() {
const [model, setModel] = useState(null)
useEffect(() => {
// 这个外部拿不到model更新
setModel({ a: 1 });
// 这个外部可以拿到更新
setTimeout(()=>{
setModel({ a: 1 });
}, 0);
}, []);
return [model, setModel]
}
export default createModel(useModel);
import { createModel } from 'hox';
import { useState, useEffect } from 'react';
import { usePersistFn } from '@umijs/hooks';
import { queryCurrent } from '@/services/user';
import useTokenModel from '@/models/useToken';
const defaultUserInfo = {
userId: null,
userName: null,
avatar: null,
};
function useUserInfo() {
const [access, setAccess] = useState({});
const [userInfo, setUserInfo] = useState({ ...defaultUserInfo });
const [userInfoLoaded, setUserInfoLoaded] = useState(false);
const { token } = useTokenModel((model) => [model.token]);
const resetUserInfo = usePersistFn(() => {
setAccess({});
setUserInfo({ ...defaultUserInfo });
setUserInfoLoaded(false);
});
const loadUserInfo = usePersistFn(() => {
queryCurrent().then((res) => {
if (res?.code === 0) {
const { userInfo: ui, accessList = [] } = res.result;
const newAccessListObj = {};
accessList.forEach((element) => {
newAccessListObj[element] = true;
});
setAccess(newAccessListObj);
setUserInfo({ ...ui });
setUserInfoLoaded(true);
}
});
});
useEffect(() => {
if (token === null) {
resetUserInfo();
} else {
loadUserInfo();
}
}, [token]);
return {
access,
userInfo,
userInfoLoaded,
};
}
export default createModel(useUserInfo);
报错信息如下:
Warning: Cannot update a component (`Executor`) while rendering a different component (`Executor`). To locate the bad setState() call inside `Executor`, follow the stack trace as described in https://fb.me/setstate-in-render
in Executor
具体报错是在执行
setAccess(newAccessListObj);
setUserInfo({ ...ui });
setUserInfoLoaded(true);
不知道为何啊
为了支持 TypeScript 的类型推导,同时保持对 JavaScript 用户的使用简单性,需要对 API 做一些调整
用户登录之后,全局保存用户信息
之前都是用dva
,请求到用户信息之后,可以在effects
中使用payload
将用户信息存到redux
以供全局使用。hox
不能带参数,那我要保存接口返回的信息要怎么做?
import { useState } from 'react';
import { createModel } from 'hox';
function useTopMenuModel() {
const [menuList, setMenuList] = useState([]);
const saveMenuList = payload => setMenuList(payload);
return { menuList, saveMenuList };
}
export default createModel(useTopMenuModel);
import useTopMenuModel from '@/hox/menuModel';
const modelData = useTopMenuModel();
console.log(modelData);
modelData.saveMenuList(menuList); // menuList是接口返回的信息
第一次用hooks,在网上也没找到相关问题,劳烦大佬们答疑下,谢谢~
类似这样实现懒加载是否可行?
export function createLazyModel<T, P>(hook: ModelHook<T, P>, hookArg?: P) {
const container = new Container(hook);
const exe = () => {
container.hasRun = true;
render(
<Executor
onUpdate={val => {
container.data = val;
container.notify();
}}
hook={() => hook(hookArg)}
/>
);
}
const useModel: UseModel<T> = depsFn => {
// ...
useAction(() => {
if (!container.hasRun) {
exe();
}
}, []);
return state!;
};
return useModel;
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.