|
| 1 | +# WebCell v2 to v3 migration |
| 2 | + |
| 3 | +## "state" concept has been totally dropped |
| 4 | + |
| 5 | +**WebCell v3** is heavily inspired by [the **Local Observable State** idea of **MobX**][1], and [not only React][2], Web Components can be much easier to manage the **Inner State & Logic**, without any complex things: |
| 6 | + |
| 7 | +1. State type declaration |
| 8 | +2. `this.state` declaration & its type annotation/assertion |
| 9 | +3. `this.setState()` method calling & its callback |
| 10 | +4. confusive _Hooks API_... |
| 11 | + |
| 12 | +Just declare a **State Store class** as what the **Global State Managment** does, and initial it on the `this` (a **Web Component instance**). Then use the state, and observe them, as [MobX][3]'s usual, everything is done. |
| 13 | + |
| 14 | +```diff |
| 15 | +import { |
| 16 | + component, |
| 17 | ++ observer, |
| 18 | +- mixin, |
| 19 | ++ WebCell, |
| 20 | + createCell, |
| 21 | + Fragment |
| 22 | +} from 'web-cell'; |
| 23 | ++import { observable } from 'mobx'; |
| 24 | + |
| 25 | +-interface State { |
| 26 | ++class State { |
| 27 | ++ @observable |
| 28 | + key: string; |
| 29 | +} |
| 30 | + |
| 31 | +@component({ |
| 32 | + tagName: 'my-tag' |
| 33 | +}) |
| 34 | ++@observer |
| 35 | +-export class MyTag extends mixin<{}, State>() { |
| 36 | ++export class MyTag extends WebCell() { |
| 37 | +- state: Readonly<State> = { |
| 38 | +- key: 'value' |
| 39 | +- }; |
| 40 | ++ state = new State(); |
| 41 | + |
| 42 | +- render({}: any, { key }: State) { |
| 43 | ++ render() { |
| 44 | ++ const { key } = this.state; |
| 45 | + |
| 46 | + return <>{value}</>; |
| 47 | + } |
| 48 | +} |
| 49 | +``` |
| 50 | + |
| 51 | +At the same time, `shouldUpdate() {}` life-cycle has been dropped. You just need to control the logic before states changed in your `State` class methods. |
| 52 | + |
| 53 | +## DOM properties become observable data |
| 54 | + |
| 55 | +**DOM properties** aren't like React's props, they're **reactive**. They are not only responsible to **update Component views**, but also **synchronize with HTML attriutes**. |
| 56 | + |
| 57 | +MobX's [`@observable`][4] & [`reaction()`][5] are awesome APIs to implement these above with clear codes, so we add `mobx` package as a dependency: |
| 58 | + |
| 59 | +```shell |
| 60 | +npm install mobx@5 |
| 61 | +``` |
| 62 | + |
| 63 | +On the other hand, [`mobx-web-cell` adapter][6] has been merged into the core package. And cause of replacing **Prototype Overwrite** with **Class Inheritance** to refactor **Class Mixins**, `@observer` decorator should follow strict order to make observation work: |
| 64 | + |
| 65 | +```diff |
| 66 | +import { |
| 67 | + WebCellProps, |
| 68 | + component, |
| 69 | + attribute, |
| 70 | +- watch, |
| 71 | ++ observer, |
| 72 | +- mixin, |
| 73 | ++ WebCell, |
| 74 | + createCell, |
| 75 | + Fragment |
| 76 | +} from 'web-cell'; |
| 77 | +-import { observer } from 'mobx-web-cell'; |
| 78 | ++import { observable } from 'mobx'; |
| 79 | + |
| 80 | +export interface MyTagProps extends WebCellProps { |
| 81 | + count?: number |
| 82 | +} |
| 83 | + |
| 84 | +@component({ |
| 85 | + tagName: 'my-tag' |
| 86 | +}) |
| 87 | +@observer |
| 88 | +-export class MyTag extends mixin<MyTagProps>() { |
| 89 | ++export class MyTag extends WebCell<MyTagProps>() { |
| 90 | + @attribute |
| 91 | +- @watch |
| 92 | ++ @observable |
| 93 | + count = 0; |
| 94 | + |
| 95 | +- render({ count }: MyTagProps) { |
| 96 | ++ render() { |
| 97 | ++ const { count } = this; |
| 98 | + |
| 99 | + return <>{count}</>; |
| 100 | + } |
| 101 | +} |
| 102 | +``` |
| 103 | + |
| 104 | +## control Render Target with Shadow DOM Mode option |
| 105 | + |
| 106 | +### render to `children` |
| 107 | + |
| 108 | +```diff |
| 109 | +import { |
| 110 | + component, |
| 111 | +- mixin |
| 112 | ++ WebCell |
| 113 | +} from 'web-cell'; |
| 114 | + |
| 115 | +@component({ |
| 116 | + tagName: 'my-tag', |
| 117 | +- renderTarget: 'children' |
| 118 | +}) |
| 119 | +-export class MyTag extends mixin() { |
| 120 | ++export class MyTag extends WebCell() { |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | +### render to `shadowRoot` |
| 125 | + |
| 126 | +```diff |
| 127 | +import { |
| 128 | + component, |
| 129 | +- mixin |
| 130 | ++ WebCell |
| 131 | +} from 'web-cell'; |
| 132 | + |
| 133 | +@component({ |
| 134 | + tagName: 'my-tag', |
| 135 | +- renderTarget: 'shadowRoot' |
| 136 | ++ mode: 'open' |
| 137 | +}) |
| 138 | +-export class MyTag extends mixin() { |
| 139 | ++export class MyTag extends WebCell() { |
| 140 | +} |
| 141 | +``` |
| 142 | + |
| 143 | +## move Shadow CSS injection into `render()` |
| 144 | + |
| 145 | +This makes **Shadow CSS** to react with the data of component instances. |
| 146 | + |
| 147 | +```diff |
| 148 | ++import { stringifyCSS } from 'web-utility'; |
| 149 | +import { |
| 150 | + component, |
| 151 | +- mixin |
| 152 | ++ WebCell |
| 153 | +} from 'web-cell'; |
| 154 | + |
| 155 | +@component({ |
| 156 | + tagName: 'my-tag', |
| 157 | +- renderTarget: 'shadowRoot', |
| 158 | ++ mode: 'open', |
| 159 | +- style: { |
| 160 | +- ':host(.active)': { |
| 161 | +- color: 'red' |
| 162 | +- } |
| 163 | +- } |
| 164 | +}) |
| 165 | +-export class MyTag extends mixin() { |
| 166 | ++export class MyTag extends WebCell() { |
| 167 | + render() { |
| 168 | + return <> |
| 169 | ++ <style> |
| 170 | ++ {stringifyCSS({ |
| 171 | ++ ':host(.active)': { |
| 172 | ++ color: 'red' |
| 173 | ++ } |
| 174 | ++ })} |
| 175 | ++ </style> |
| 176 | + test |
| 177 | + </>; |
| 178 | + } |
| 179 | +} |
| 180 | +``` |
| 181 | + |
| 182 | +## rename some APIs |
| 183 | + |
| 184 | +[JSDoc's `@deprecated` hints][7] will lead your way to rename them: |
| 185 | + |
| 186 | +1. `mixin()` => `WebCell()` |
| 187 | +2. `mixinForm()` => `WebField()` |
| 188 | +3. `@watch` => `@observable` |
| 189 | + |
| 190 | +## Appendix: v3 prototype |
| 191 | + |
| 192 | +1. https://codesandbox.io/s/web-components-jsx-i7u60?file=/index.tsx |
| 193 | +2. https://codesandbox.io/s/mobx-lite-791eg?file=/src/index.ts |
| 194 | + |
| 195 | +[1]: https://github.com/mobxjs/mobx/blob/mobx4and5/docs/refguide/observer-component.md#local-observable-state-in-class-based-components |
| 196 | +[2]: https://blog.cloudboost.io/3-reasons-why-i-stopped-using-react-setstate-ab73fc67a42e |
| 197 | +[3]: https://github.com/mobxjs/mobx/tree/mobx4and5/docs |
| 198 | +[4]: https://github.com/mobxjs/mobx/blob/mobx4and5/docs/refguide/observable-decorator.md |
| 199 | +[5]: https://github.com/mobxjs/mobx/blob/mobx4and5/docs/refguide/reaction.md |
| 200 | +[6]: https://github.com/EasyWebApp/WebCell/tree/v2/MobX |
| 201 | +[7]: https://jsdoc.app/tags-deprecated.html |
0 commit comments