Repository navigation
升级正式 Calcit 0.31:对齐文档站依赖并保留导航合同 #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,2 @@ | ||
| nodeLinker: node-modules | ||
| npmMinimalAgeGate: 60 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,10 @@ | ||
|
|
||
| {} (:calcit-version |0.30.1) | ||
| {} (:calcit-version |0.31.0) | ||
| :version |0.0.4 | ||
| :dependencies $ {} (|Respo/reel.calcit |0.6.41) | ||
| |Respo/respo-markdown.calcit |0.4.59 | ||
| |Respo/respo-router.calcit |0.8.37 | ||
| |Respo/respo-ui.calcit |0.8.0 | ||
| |Respo/respo.calcit |0.16.124 | ||
| |calcit-lang/docs-workflow |0.0.37 | ||
| :dependencies $ {} (|Respo/reel.calcit |0.7.0-alpha.2) | ||
| |Respo/respo-markdown.calcit |0.5.0-alpha.3 | ||
| |Respo/respo-router.calcit |0.9.0-alpha.1 | ||
| |Respo/respo-ui.calcit |0.9.0-alpha.2 | ||
| |Respo/respo.calcit |0.17.0-alpha.2 | ||
| |calcit-lang/docs-workflow |0.0.38 | ||
| |calcit-lang/js-ffi |0.2.5 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,151 +1,58 @@ | ||
| ## Component States | ||
|
|
||
| Unlike React, states in Respo is maintained manually for stablility during hot code swapping. | ||
| At first, states is a HashMap inside the store: | ||
| A component state branch is an open Map with a `:cursor` path and optional | ||
| `:data`. The state value can differ between widgets. Keep that heterogeneous | ||
| boundary explicit; decode the fields a widget consumes before using them as a | ||
| business model. A missing Map key returns `Option :none`, not an implicit nil. | ||
|
|
||
| ```cirru | ||
| defatom *store {} | ||
| :states $ {} | ||
| ``` | ||
|
|
||
| By design, if states is added, you would a tree: | ||
|
|
||
| ```cirru | ||
| {} | ||
| :states $ {} | ||
| :data $ {} | ||
| :todolist $ {} | ||
| :data $ {} | ||
| :input "|xyz..." | ||
| "task-1-id" $ {} | ||
| :data $ {} | ||
| :draft "|xxx..." | ||
| "task-2-id" $ {} | ||
| :data $ {} | ||
| :draft "|yyy..." | ||
| "task-2-id" $ {} | ||
| :data $ {} | ||
| :draft "|zzz.." | ||
| ``` | ||
|
|
||
| `:data` is a special field for holding state of each component. | ||
| It has to be a tree since Virtual DOM is a tree. | ||
| You also notice that its structure is simpler than a DOM tree, it only contains states. | ||
|
|
||
| `respo.core/>>` is the "picking branch" function. It also maintains a `:cursor` field. | ||
|
|
||
| When you call `(>> states :todolist)`, you get new `states` variable for a child component: | ||
|
|
||
| ```cirru | ||
| {} | ||
| ; "generated cursor, nil at top level" | ||
| :cursor $ [] :todolist | ||
| ; "state at current level" | ||
| :data $ {} | ||
| :input "|xyz..." | ||
|
|
||
| ; states for children | ||
| "task-1-id" $ {} | ||
| :data $ {} | ||
| :draft "|xxx..." | ||
| "task-2-id" $ {} | ||
| :data $ {} | ||
| :draft "|yyy..." | ||
| "task-2-id" $ {} | ||
| :data $ {} | ||
| :draft "|zzz.." | ||
| ``` | ||
|
|
||
| Then you call `(>> states "task-1-id")` and you get new `states` for child "task-1": | ||
|
|
||
| ```cirru | ||
| {} | ||
| ; "generated cursor" | ||
| :cursor $ [] :todolist "|task-1-id" | ||
|
|
||
| ; "state of task-1" | ||
| :data $ {} | ||
| :draft "|xxx..." | ||
| ``` | ||
|
|
||
| For state inside each component, it's `nil` at first. | ||
| You want to have an initial state, use `or` to provide one. | ||
| `respo.core/>>` selects a branch and extends its cursor. This small example uses | ||
| Tag keys, matching this documentation app's checked `List<Tag>` operation path: | ||
|
|
||
| ```cirru | ||
| defcomp comp-task (states) | ||
| let | ||
| cursor (:cursor states) | ||
| state $ or (:data states) $ {} | ||
| :draft "|empty" | ||
| (div {})) | ||
| let | ||
| states $ {} (:cursor $ []) (:task $ {} (:data |Draft)) | ||
| branch $ respo.core/>> states :task | ||
| assert= (Option :some $ [] :task) (get branch :cursor) | ||
| assert= (Option :some |Draft) (get branch :data) | ||
| ``` | ||
|
|
||
| By accessing `(:data states)`, you get `nil`, so `&{} :draft "|empty"` is used. | ||
| After there's data in states, you get data that was set. | ||
| A missing branch starts with an empty cursor-bearing Map. Do not replace a | ||
| present false, zero or empty string state with an arbitrary default. Match the | ||
| Option returned by `get`; decode a present value according to the widget's | ||
| actual state shape. | ||
|
|
||
| Then you want to update component s:tate | ||
| A Respo event handler receives two arguments: the event Map and the application | ||
| dispatch function. That dispatch function accepts **one** entry `dispatch-op` | ||
| value and returns Unit. Two handler arguments do not imply a two-argument | ||
| state dispatcher. Wrap the cursor and new state in the app's single operation: | ||
|
|
||
| ```cirru | ||
| defcomp comp-task (states) | ||
| let | ||
| cursor (:cursor states) | ||
| state $ or (:data states) $ {} | ||
| :draft "|empty" | ||
| div $ {} | ||
| :on-click $ fn (e dispatch!) | ||
| dispatch! cursor (assoc state :draft "|New state") | ||
| fn (event d!) | ||
| d! $ :: :states cursor new-state | ||
| , &unit | ||
| ``` | ||
|
|
||
| So `(dispatch! cursor state)` sends the new state. | ||
| This is a callback fragment in a context that already provides `cursor` and | ||
| `new-state`, not a standalone program. This site's application boundary checks | ||
| the raw Enum and payload before constructing the published nominal Op. | ||
| A custom callback such as `on-select(path, d!)` can also have two arguments; | ||
| that does not change the arity of the `d!` value passed to it. | ||
|
|
||
| The last step to to update global states with `respo.cursor/update-states`. | ||
| Internally `(dispatch! cursor op-data)` will be transformed to `(dispatch! :states ([] cursor op-data))`. | ||
| And then in `updater` you add: | ||
| For the tree update itself, use the current three-argument helper: | ||
|
|
||
| ```cirru | ||
| case-default op | ||
| ; other actions | ||
| do store | ||
|
|
||
| ; "where op-data is [cursor new-state]" | ||
| :states (update-states store op-data) | ||
| let | ||
| updated $ respo.cursor/update-state-tree ({}) ([] :task) |Edited | ||
| assert= ({} $ :task $ {} (:data |Edited)) updated | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| Let's wrap it. First we have empty states inside store: | ||
|
|
||
| ```cirru | ||
| {} | ||
| :states $ {} | ||
| ``` | ||
|
|
||
| And it is passed to `(comp-todolist (>> states :todolist) data)`, | ||
| and then passed to `(comp-task (>> states (:id task)) task)`. | ||
|
|
||
| In `comp-todolist`, `(:data states)` provides component state, `(:cursor states)` provides its cursor. | ||
| Call `(dispatch! cursor {:input "|New draft"})` and global store will become: | ||
|
|
||
| ```cirru | ||
| {} | ||
| :states $ {} | ||
| :todolist $ {} | ||
| :data $ {} | ||
| :input "|New draft" | ||
|
|
||
| ``` | ||
|
|
||
| In `comp-task` of "task-1", you also get `state` and `cursor`, so call `(dispatch! cursor {:draft "New text"})` you will get: | ||
|
|
||
| ```cirru | ||
| {} | ||
| :states $ {} | ||
| :todolist $ {} | ||
| :data $ {} | ||
| :input "|New draft" | ||
| "task-1-id" $ {} | ||
| :data $ {} | ||
| :draft "|New text" | ||
| ``` | ||
| `update-state-tree` and `respo.cursor/update-states` expose open boundaries; | ||
| `update-states` also takes three arguments: store, cursor, new state. Their | ||
| results are not automatically proof of a nominal Store. This app's published | ||
| Docs Workflow updater checks the updated state Map before assigning it into | ||
| Store. Its app dispatcher validates cursors and hydrated stores before building | ||
| `docs-workflow.schema/Op`, then typed Reel records that operation. | ||
|
|
||
| And that's how Respo states is maintained. | ||
| Use the actual checked `app.main/dispatch!` and `docs-workflow.updater/updater` | ||
| definitions as integration references. The removed `case-default` example and | ||
| the old two-argument `update-states` call are not current alternatives. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,27 +1,43 @@ | ||
| ## Hot swapping | ||
|
|
||
| Hot swapping is done by the compiler. What you need to do is to call `respo.core/clear-cache!` before re-rendering happens: | ||
| Use retained `defref` values and explicit watch effects. The current app keeps | ||
| its typed Reel in `app.main/*reel`; its value is immutable, while the reference | ||
| is mutable. `defatom`, `atom`, `add-watch` and `remove-watch` are retired names. | ||
| Do not add another reference around the initial store. | ||
|
|
||
| This is the initialization expression from this site's checked source: | ||
|
|
||
| ```cirru | ||
| defn reload! () | ||
| ; "clear component caches" | ||
| clear-cache! | ||
| ; "rerender DOM tree, I mean, a diff/patch loop" | ||
| render-app! | ||
| println "|Code update." | ||
| defref *reel $ typed/new-reel schema/store | ||
| ``` | ||
|
|
||
| If you don't, in the next rendering phase old element tree would be used if no argument changes found, which means Respo would still use render functions defined previously. | ||
| It belongs with the published `reel.typed/State<Op, Store>` schema attached to | ||
| `app.main/*reel`. An isolated excerpt does not carry that schema or its imports; | ||
| inspect the actual definition with `calcit query def 'app.main/*reel'` before reuse. | ||
|
|
||
| In Respo, you are asked to define `*store` explicitly. They the global states of data. As an Atom, the value inside is immutable, but the reference is mutable. During hot swapping, variables defined with `defatom` will be retained. As a result, component states are persistent even code is swapped: | ||
| The successful branch of this app's `reload!` uses the following sequence: | ||
|
|
||
| ```cirru | ||
| defatom *store $ atom | ||
| or | ||
| let | ||
| raw $ or | ||
| js/localStorage.getItem "|respo" | ||
| , "|{:data [], :states {}}" | ||
| read-string raw | ||
| , schema/store | ||
| remove-watch! *reel :changes | ||
| clear-cache! | ||
| add-watch! *reel :changes $ fn (reel prev) | ||
| render-app! | ||
| reset! *reel $ typed/refresh updater @*reel schema/store | ||
| ``` | ||
|
|
||
| Clear the component cache before refreshing Reel. Register the new callback | ||
| before that refresh so its state change renders the new code. Watch callbacks | ||
| receive both the new and previous values; they are not the application dispatch | ||
| callback. Replacing the named watch avoids retaining an obsolete callback after | ||
| reload. | ||
|
|
||
| Keep the original error branch and JavaScript `bottom-tip` `hud!` calls: build | ||
| errors must remain visible on the page. A failed build must not refresh the | ||
| running state as though it were valid. `hud!` is a host UI function, not a | ||
| replacement for `raise` or a language primitive. | ||
|
|
||
| Archive loading is a separate boundary. This app currently writes an archive | ||
| but does not restore it at startup; this migration does not enable restoration. | ||
| If adding restoration, decode the wire value into the local Store before | ||
| registering automatic saves. A parse failure must not overwrite the original | ||
| archive with a default store. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.