preface
📢 blog launch: Hiro’s blog
🍉 licensed reprint of the team’s Blog: SugarTurboS Blog
React state management exploration, which leaves a question: Does hox really smell good? I don’t know if it smells good, but here are some of my impressions after using hox…
I have to say, it is too difficult to top, finally changed the source code, to achieve a low version of the Model Tree…
The following contents are hiro’s personal feelings, only represent personal views, if there is any mistake, please also point out 🤝 ~
❓ why this column is called [KT], I this person is relatively low, column Chinese called: wide Technology deep text, K from the width of the wide, T, Technology, Technology, forced case. Yeah, I feel like I’m taking my bragging skills one step further.
The text start
I searched Hox articles online, relatively few, and most of them are introduction articles (that is, tell you what this library is and how to use it). After practicing a module in the project, IT is also a small harvest, here are some of my feelings…
How to divide granularity
I want to talk to you about how to “divide granularity”, how to understand? For example, we now have a reducer with nearly 40 fields, which should be very common. For a complicated module, it is normal to have a reducer with 30 or 40 fields
The question is how do we define the latitude of “thinness”, it has nothing to do with HOX, it is simply to make your hooks persistent, globally shared data, we are talking about: one model hooks or n model hooks?
This problem was always bothering me when I reconstructed my practice. Finally, I decided to divide it into three cases as follows:
- A field is a Model file
- Multiple fields can be defined in a useXXXModel
- A model file that allows n (n<5) usexxxModels
Here’s why:
- A field is a Model file
I don’t really agree with this granularity division. Why? If a small module has 10 fields, you should have 10 files. Really? So why am I talking about this? Because if you have a field that’s independent, that doesn’t have anything to do with other fields, and it’s complicated, and maybe you’re doing some side effects in your model, it’s probably better to write it like this, right?
Which is good or bad, ultimately depends on your own definition, according to your own favorite writing
In this case, however, you might end up with a bit too much code. For example, if your index.js component requires these 10 values, the code would look something like this
import useClassModel from '@src/model/user/useClassModel'
import useSubjectModel from '@src/model/user/useSubjectModel'
import useChapterModel from '@src/model/user/useChapterModel'
// Import 7 files...
function User() {
const { classId, changeClassId } = useClassModel()
const { subjectName, changeSubjectName } = useSubjectModel()
const { chapterName, changeChapterName } = useChapterModel()
// There is code here...
}
Copy the code
And then when you modify it, the logical code might look like this
function changeSelectClass (class) { changeClassId(class.id); // Change the class id changeClassName(class.clsname); ChangeSubjectName (class[0].subjects[0].subjectName) By default, the first subject in this class is changeSubjectCode(class[0].subjects[0].subjectCode). ChangeChapterName (class[0].subjects[0].subjectcode.chapter [0].chapterName) // The first chapter of this discipline is selected by default changeChapterCode(class[0].subjects[0].subjectCode.chapter[0].chapterCode) }Copy the code
The point is, this way, your code volume will increase, but it’s not a problem, I think this kind of code reading is ok
- Multiple fields can be defined in a useXXXModel
In this case, I recommend writing it this way for strong correlation. What is strong correlation? Such as classId and className, subjectCode and subjectName, where one changes and the other must change, can be written in a useModel. The following
function useSelectSubject() {
const [subjectCode, changeSubjectCode] = useState(undefined)
const [subjectName, changeSubjectName] = useState(undefined)
const setSubjectCode = (subjectCode: string) = > changeSubjectCode(subjectCode)
const setSubjectName = (subjectName: string) = > changeSubjectName(subjectName)
return {
subjectCode,
subjectName,
setSubjectCode,
setSubjectName,
}
}
Copy the code
Of course, you can wrap these two fields with an object, whatever you like, but I just want to express, since there is a relationship, one changes the other, can be put together
- A model file that allows n (n<5) usexxxModels
Is there a case where you have several fields that can be “strongly correlated” into n, but you don’t want to have n.js files that are common to the module?
You can’t create a new folder and write all n files as usen1Model.js, usen2Model.js, usen3Model.js.
My personal advice is to keep this file to 150 lines of code, and maybe 5 models, and break it up after 5, but that’s just my personal advice
// This is called usebasemodel.js
import { createModel } from 'hox'
function useSelectClass() {}
function useSelectSubject() {}
function useSelectChapter() {}
export default {
useSelectClass: createModel(useSelectClass),
useSelectSubject: createModel(useSelectSubject),
useSelectChapter: createModel(useSelectChapter),
}
Copy the code
You only need to import once when using it, and this is not bad in terms of readability, after all, the three useModel are related
import useBaseModel from '@src/model/user/useBaseModel'
function User() {
const { className } = useBaseModel.useSelectClass()
const { subjectName } = useBaseModel.useSelectSubject()
const { chapterName } = useBaseModel.useSelectChapter()
}
Copy the code
File directory Division
Official description of hoX features: Available anywhere, so the file directory specification of the Model layer should also be unified. Although the business reducer was also placed under the business folder when redux was used before, because of the existence of combineReducer, We can see the reducer with all imports in the store entry file index.js
But hox, when you don’t use createModel, it’s just a business hooks that are built and used on demand, which can be very difficult to maintain later, especially without dev-tools, where you don’t know which hooks are persistent, globally shared Model hooks
Is there A case where A is being developed and it starts with A business hooks that need to be globally shared, so it just wraps it in the current directory with createModel, and that’s it. Gradually it became the norm… This is the classic “broken window effect”
Broken Windows: A building with a few broken Windows may have vandals destroying more Windows if those Windows are not repaired. A wall, if some graffiti is not washed off, soon, the wall is covered with messy, ugly things
I suggest that we change the folder of store to Model, and the folder structure of Model is consistent with business.
Ecosystem of small
Github is currently a minor library for Star 519, Issues 9, but this is not a big problem because you don’t use its createModel to persist data, which is essentially a custom hooks
If there are problems, most of them are from my own logic, which is fixed by the hooks I wrote.
None Dev Tools support
This is the pain point!! After we use the createModel package, how do we know if the data is really persisted and shared globally?
The normal operation would be to import the data source in the component and then print console.log, but with redux, you can see the state tree directly under the redux-devTools plugin. That’s convenient.
For example, if I want to see if the desired field exists under user, I just need to look at the State Tree in the plugin
In another scenario, for example, we send A request in component A and then modify some data in the model, which is only used in component B but not in component A. Without an error, we have no way of knowing whether the data is really modified
The solution
It’s really sad. I can’t really import and go to console.log, can I? So with Pengge pondering, directly modify the source code, and then implement a simple Model tree…
- Inject the namespace for each hooks
Return {classId, changeClassId} from useSelectClass. This is the result of which hooks are fixed to createModel. To do this, we inject a namespace for this hooks
function useSelectClass() {}
useSelectClass.namespace = 'useSelectClass'
export default {
useSelectClass: createModel(useSelectClass),
}
Copy the code
Then modify the createModel source code to pass only two fields to the Executor, and now give it namespace support
// createModel.js
render(
<Executor
onUpdate={(val)= >{container.data = val container.notify()}} hook={() => hook(hookArg)} namespace={hook. Namespace}Copy the code
- After all, we only want to display a model tree. Then mount the hooks to the window
function Executor(props) {
// The original logic code, blah blah blah XXXX
// The following code is new
if (!window.hox) {
window.hox = {}
}
let keys = Object.keys(data)
let maps = {}
keys.forEach((key) = > {
if (typeofdata[key] ! = ='function') {
maps[key] = data[key]
}
})
window.hox = { ... window.hox, [props.namespace]: { ... maps },// Use namespace as key and function as value
}
return null
}
Copy the code
- Object. DefineProperty overrides set and get to listen for window.hox changes
If we print window.hox, we have the data, and we are closer to success, but the problem is, after we change the model value, we need to respond in real time. Should we do this? Remember to see the principle of Vue bidirectional binding before, oh no, there is object.defineProperty to use
In the dev-tools component we wrote, listen on window.hox, store the latest model data to state, and then match it with the Ant Design Tree component to render
componentDidMount() {
window.b = {};
const _this = this;
Object.defineProperty(window.'hox', {
set: function(value) {
window.b = value;
_this.setState({
model: value
});
},
get: function() {
return window.b; }}); }Copy the code
Let a = {}, then set a = value, but there is a problem. What is the problem? Friends can think about it, or practice a wave
- Recursively traverses, constructs the data required by the Tree component, and renders
This is the most time-consuming, we need to convert this data format to the data format Tree wants, write many times did not write correctly, behind or pengge gave a tip, thank Pengge
// Our data
window.hox = {
useUserModel: {
name: 'Pang Dao Kuan'.school: [{s_name: 'XXX university'.time: '2015-2019'}, {s_name: 'XXX high school'.time: '2012-2015'],},currentCompany: {
c_name: 'CVTE'.c_job: 'Front End Engineer',,}}}Copy the code
// Ant Design Tree data
antdTree = [
{
title: 'useUserModel'.key: '0'.children: [{title: 'name'.key: '0-0'.children: []}, {title: 'school'.key: '0-1'.children: [{title: 's_name'.key: '0-1-0'.children: [],},],},]Copy the code
I’m not going to write all of this up here, but the question is, how do you iterate? Can you think about it? Let’s just go to the next step
format = (model) = > {
let result = []
const deep = (children, value, idx) = > {
Object.keys(value).forEach((key, index) = > {
let temp = {}
temp.key = `${idx}-${index}`
// This is my handling of the title, because it is not function/object, directly render value after
temp.title = this.renderTitle(value[key], key)
temp.children = []
if (this.checkIsObjectOrArray(value[key])) {
// If it's an object or array, recurse
deep(temp.children, value[key], temp.key)
}
children.push(temp)
})
}
Object.keys(model).forEach((key, index) = > {
let temp = {}
temp.key = index
temp.title = this.renderTitle(model[key], key)
temp.children = []
if (this.checkIsObjectOrArray(model[key])) {
deep(temp.children, model[key], index)
}
result.push(temp)
})
return result
}
Copy the code
The effect is shown in 👇
- It’s almost done. Let’s refine the dev-Tools component a little bit. Refer to the redux plugin
This is still a little bit of a problem, the latter part of the plan to optimize a wave, the problem is not big, we know the idea is good ~
For questions
If I code this way, do you think there’s a problem?
function useSelectClass() {
const [classId, setClassId] = useState(' ')
const changeClassId = (classId) = > setClassId(classId)
useEffect((a)= > {
// Change the classId file
const data = handle()
setTimeout((a)= > {
setClassId(data)
}, 10000)})return { classId, changeClassId }
}
export default createModel(useSelectClass)
Copy the code
Do you think it would be a problem if I wrote this? Yes, if I’m the user, and I take the classId value, then within 10 seconds, I take undefined, and then 10 seconds later, all of a sudden the classId changes; Another possibility is that you rely on classId, and when classId changes you send a request, and 10 seconds later you find that it’s ok, and 10 seconds later, oh no, why suddenly you send a request again?
So I think it’s important to discipline that hooks on model don’t write side effects, it should keep getValue, setValue
Reducer is a pure function. Reducer is a pure function. If you don’t know the pure function, try to make it popular
Another issue, not a disadvantage, that I would like to discuss: decentralized dev-tools-free management of store versus Hox, as opposed to redux’s centralized management
The last
If I was a newcomer, I would only know react, redux, DVA, mobx. I just wanted to write small projects, so I would choose HOx
For small projects, few fields need to be stored in the store, so with the crude version of dev-tools, HOX may be more suitable for this scenario. If redux set of action->saga-> Reducer is introduced, the initial investment is relatively large. But for big projects, use Redux because redux is mature and the ecosystem is rich
Personally, I still think the design idea of HOX is quite good, but I am worried about whether it will be the product of KPI, and no one will maintain or update it in the later stage.
Okay, for Hox, that’s it for today. Let’s go. Finally, the suggestion that interested can go to see the source code, can indeed, go up knowledge…
A link to the
- hox
- Team blog
- Hiro’s blog
- 【KT】 Explore React state management
- 【KT】 Easy to get Redux source code interpretation and programming art
Right, without the original blogger’s consent, forbid to reprint, otherwise I see a, report a, a little hard ~ ~