未验证 提交 5592e64c 编写于 作者: 退 退之 提交者: GitHub

Fix with mobx (#11907)

* fix: with-mobx

* refactor: up link

* refactor: npm run prettier-fix

* refactor: server side render

* Updated store implementation and pages

* Updated readme
Co-authored-by: Nwangcheng <wangcheng@deepblueai.com>
Co-authored-by: NLuis Alvarez <luis@zeit.co>
上级 6188c79b
# MobX example
Usually splitting your app state into `pages` feels natural but sometimes you'll want to have global state for your app. This is an example on how you can use mobx that also works with our universal rendering approach. This is just a way you can do it but it's not the only one.
Usually splitting your app state into `pages` feels natural but sometimes you'll want to have global state for your app. This is an example on how you can use mobx that also works with our universal rendering approach.
In this example we are going to display a digital clock that updates every second. The first render is happening in the server and then the browser will take over. To illustrate this, the server rendered clock will have a different background color than the client one.
![](http://i.imgur.com/JCxtWSj.gif)
To illustrate SSG and SSR, go to `/ssg` and `/ssr`, those pages are using Next.js data fetching methods to get the date in the server and return it as props to the page, and then the browser will hydrate the store and continue updating the date.
The clock, under `components/Clock.js`, has access to the state using the `inject` and `observer` functions from `mobx-react`. In this case Clock is a direct child from the page but it could be deep down the render tree.
This example is a mobx port of the [with-redux](https://github.com/zeit/next.js/tree/master/examples/with-redux) example. Decorator support is activated by adding a `.babelrc` file at the root of the project:
The trick here for supporting universal mobx is to separate the cases for the client and the server. When we are on the server we want to create a new store every time, otherwise different users data will be mixed up. If we are in the client we want to use always the same store. That's what we accomplish on `store.js`.
```json
{
"presets": ["next/babel"],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose": true }]
]
}
```
The clock, under `components/Clock.js`, has access to the state using the `inject` and `observer` functions from `mobx-react`. In this case Clock is a direct child from the page but it could be deep down the render tree.
## Deploy your own
......@@ -58,7 +48,3 @@ yarn dev
```
Deploy it to the cloud with [ZEIT Now](https://zeit.co/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
### Rehydrating with server data
Be aware that data that was used on the server (and provided via one of Next.js data fetching methods) will be stringified in order to rehydrate the client with it. That means, if you create a store that is, say, an `ObservableMap` and give it as prop to a page, then the server will render appropriately. But stringifying it for the client will turn the `ObservableMap` to an ordinary JavaScript object (which does not have `Map`-style methods and is not an observable). So it is better to create the store as a normal object and turn it into a `Observable` in the `render()` method. This way both sides have an `Observable` to work with.
export default props => {
import { observer } from 'mobx-react'
const Clock = observer(props => {
return (
<div className={props.light ? 'light' : ''}>
{format(new Date(props.lastUpdate))}
{props.timeString}
<style jsx>{`
div {
padding: 15px;
......@@ -17,9 +18,5 @@ export default props => {
`}</style>
</div>
)
}
const format = t =>
`${pad(t.getUTCHours())}:${pad(t.getUTCMinutes())}:${pad(t.getUTCSeconds())}`
const pad = n => (n < 10 ? `0${n}` : n)
})
export default Clock
......@@ -19,7 +19,7 @@ class Page extends React.Component {
<div>
<h1>{this.props.title}</h1>
<Clock
lastUpdate={this.props.store.lastUpdate}
timeString={this.props.store.timeString}
light={this.props.store.light}
/>
<nav>
......
......@@ -7,11 +7,11 @@
"start": "next start"
},
"dependencies": {
"mobx": "^2.7.0",
"mobx-react": "^4.0.4",
"mobx": "^5.15.4",
"mobx-react": "^6.2.2",
"next": "latest",
"react": "^16.7.0",
"react-dom": "^16.7.0"
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
"license": "ISC",
"devDependencies": {
......
import { useMemo, useEffect } from 'react'
import { Store } from '../store'
import { Provider } from 'mobx-react'
import { useStore } from '../store'
export default function App({ Component, pageProps }) {
const store = useMemo(() => {
return new Store()
}, [])
useEffect(() => {
// If your page has Next.js data fetching methods returning a state for the Mobx store,
// then you can hydrate it here.
const { initialState } = pageProps
if (initialState) {
store.hydrate(initialState)
}
}, [store, pageProps])
const store = useStore(pageProps.initialState)
return (
<Provider store={store}>
......
import Page from '../components/Page'
export default function SSG() {
return <Page title="Index Page" linkTo="/other" />
}
// If you build and start the app, the date returned here will have the same
// value for all requests, as this method gets executed at build time.
export function getStaticProps() {
return { props: { initialState: { lastUpdate: Date.now() } } }
}
import Page from '../components/Page'
export default function SSR() {
return <Page title="Index Page" linkTo="/other" />
}
// The date returned here will be different for every request that hits the page,
// that is because the page becomes a serverless function instead of being statically
// exported when you use `getServerSideProps` or `getInitialProps`
export function getServerSideProps() {
return { props: { initialState: { lastUpdate: Date.now() } } }
}
import { action, observable } from 'mobx'
import { action, observable, computed, runInAction } from 'mobx'
import { useStaticRendering } from 'mobx-react'
import { useMemo } from 'react'
// eslint-disable-next-line react-hooks/rules-of-hooks
useStaticRendering(typeof window === 'undefined')
export class Store {
let store
class Store {
@observable lastUpdate = 0
@observable light = false
hydrate(serializedStore) {
this.lastUpdate =
serializedStore.lastUpdate != null
? serializedStore.lastUpdate
: Date.now()
this.light = !!serializedStore.light
}
@action start = () => {
this.timer = setInterval(() => {
this.lastUpdate = Date.now()
this.light = true
runInAction(() => {
this.lastUpdate = Date.now()
this.light = true
})
}, 1000)
}
@computed get timeString() {
const pad = n => (n < 10 ? `0${n}` : n)
const format = t =>
`${pad(t.getUTCHours())}:${pad(t.getUTCMinutes())}:${pad(
t.getUTCSeconds()
)}`
return format(new Date(this.lastUpdate))
}
stop = () => clearInterval(this.timer)
hydrate = data => {
if (!data) return
this.lastUpdate = data.lastUpdate !== null ? data.lastUpdate : Date.now()
this.light = !!data.light
}
}
export function initializeStore(initialData = null) {
const _store = store ?? new Store()
// If your page has Next.js data fetching methods that use a Mobx store, it will
// get hydrated here, check `pages/ssg.js` and `pages/ssr.js` for more details
if (initialData) {
_store.hydrate(initialData)
}
// For SSG and SSR always create a new store
if (typeof window === 'undefined') return _store
// Create the store once in the client
if (!store) store = _store
return _store
}
export function useStore(initialState) {
const store = useMemo(() => initializeStore(initialState), [initialState])
return store
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册