Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

A Router made for Redux and made for universal apps! stop using the router as a controller... its just state!

NotificationsYou must be signed in to change notification settings

Agamennon/redux-tiny-router

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

67 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

A Router made for Redux and made for universal apps! stop using the router as a controller... it's just state!

It's simple, and it's small, example app inreact-redux-tiny

warning! changing fast, for the new stuff look at thechangelog , readme a little behind

Using client side OPTION1 (store enhancer) OPTION2 (bring all yourself) at the end.

Create your store with the redux-tiny-router applyMiddleware

import{createStore}from'redux';import{applyMiddleware}from'redux-tiny-router'import*asyourReducersfrom'./someplace'importyourMiddlewarefrom'./someotherplace'// Don't combine the reducers the middleware does this for youletmiddleware=[yourMiddleware];//and others you usevarfinalCreateStore=applyMiddleware(...middleware)(createStore);store=finalCreateStore(yourReducers,{});//just pass your reducers object

and you are DONE!

If you enter any paths to your url you will notice that you also have a router object on your state containing relevant information about the current route,but how to change the route inside the app?

redux-tiny-router action creators

Yup, you call an action, first import the router actions:

import{tinyActions}from'redux-tiny-router'//navigates to a route with optional search objectdispatch(tinyActions.navigateTo('/somepath',{message:1}));

The router will navigate to (/somepath?message=1) and set the router object with the info, for that it fires an actiontype:'ROUTER_NAVIGATION'that you can use to intercept the action on your middleware and do all sorts of interesting things, more later...

Some more cool actions:

preventNavigation();//bonus! call this with a string and will prevent the user from navigating away from the page too!

Does what it says (it also blocks forward and back), after you call this you lock navigation, useful if the user is on a form and you want to warn him about pending changes,if the user attempts to navigate, it fires and actiontype:PREVENTED_NAVIGATION_ATTEMPTED that will set a field in your router withthe attempted route, you actually don't need to worry about this, you can just check on your app if the value on the router.attemptedOnPreventcontains a value (this value is the attempted url) in this case you can show a pop-up warning the user of pending changes.But what if the users whats to navigate away?

doPreventedNavigation();

You call this action!, it will read the value from router.attemptedOnPrevent and navigate there! (it handles back and forward buttons just fine)

allowNavigation();

That just allows navigation again. if you want to handle the navigate after confirm implementation yourself.

Basic routing

You could just do this, inside your react app,

@connect((state)=>{return{router:state.router}})constComp=React.createClass({render(){switch(this.props.router.path){case'/':return<Home/>;case'/other':return<Other/>default:return<NotFound/>;}}});

The basic idea is this, no more controller components, it's just state, the reducer in redux-tiny-router, will feed your statewith a router object that represent all the nuances of the url automatically

What do i get in this router object?

for an url likesome/cool/path?name=gui

"router":{"url":"/some/cool/path?name=gui","src":null,"splat":null,"params":{},"path":"/some/cool/path","paths":["/","some","cool","path"],"query":{"name":"gui"}}

Theurl property hold the exact url you see in the browser,src,splat, andparams will only have a value ifyou specify some route configuration (more on this later) if your routes are not too complicated you will have no needfor those,path it's the url minus the query string?name=gui in this example,paths is an array with all individual elementsandquery holds the query string turned into a object.

You are free to use any of those to decide what component you will render, so this brings the "controlling"back to your app.

Configuring routes, in case you need it

redux-tiny-router internally uses a slightly modified version of a tiny route matching library calledhttp-hashwith that you can choose to define some routes, those definitions will populate the router objectsrcsplat andparams properties,lets take a look:

First bring into your project the router utils, naturally configure your routes before you need'em

import{utils}from'redux-tiny-router';//this will configure a route (the second part of the path will be a parameter called test)//This matches /foo/<anything> but "/"  it will match /foo/somestuf but will not match /foo/somestuff/morestuffutils.set('/foo/:test/')

If you navigate to/foo/cool the router now knows, since you configured a matching route, how to populatesrc,in this case it will be set to/foo/:test/ src hold what pattern was matched with the url, this is quite usefulfor your react app to decide what to render (examples later...)params will have the object containing the route params,in this case{test:cool}, splat will benull.The router does not care if the url matches the route, if it does not,you just don't get values forsrcparams andsplat. Think about route definitions as teaching the router how to extractextra information that you need.

What is a splat? well it's the wild-card *, lest add another route definition.

//this will map to /test/<anything> but "/">/<anything> ...utils.set('/foo/:test/*')

Let's trow/foo/some/long/stuff as a url, nowsrc will be/foo/:test/* params{test:some}and splat/long/stuff (splat is anything that came after the*)

For convenience you can useutils.setRoutes pass an array of definitions to set them all with one call:

utils.setRoutes(['/foo/:test/','/foo/:test/*'...  ...  ...]);

A more specific definition have precedence over a broad definition so/foo/something in the above definitionscould match both route definitions, butsrc will be set to/foo/:test/ as it's more specific. (the order of route definitions does not matter).

I told you thatsrc is useful, well any pace of state from router can be useful butsrc is specially coollets look of how to use this in a react app (nesting routes):

Consider the url/foo/some/more

//before...utils.setRoutes(['/','/foo/*','/foo/some']);constComp=React.createClass({render(){switch(this.props.router.src){//looking at src propertycase'/':return<Home/>;case'/foo/*':return<Foo/>case'/foo/some'://this have to be here as a more specific route like /foo/some  would be matched here (src would = '/foo/some')return<Foo/>default:return<NotFound/>;}}});

Foo could be:

constFoo=React.createClass({render(){switch(this.props.router.splat){//notice SPLATcase'/some':return<Some/>case'/some/more'return<More/>default:return<NotFound/>;}}});

That would render</More> Just remember that this example is somewhat arbitrary, in this case you don't even "need" to define routes, you could have usedrouter.paths[1] on Comp androuter.paths[2] on Foo, like so:

constComp=React.createClass({render(){varpaths=this.props.router.paths;if(paths[0]==='/')return<Home/>if(paths[1]==='/foo')return<Foo/>return<NotFound/>}});

on Foo

constFoo=React.createClass({render(){varpaths=this.props.router.paths;if(paths[3]==='some')return<Some/>if(paths[4]==='more')return<More/>return<NotFound/>}});

Remember route definitions only add more details, you can use any peace of state you need and any javascript knowledge you have to render your app,but just to give you yet more power, to guarantee you can do anything i could think of, have a look at this puppyutils.match(definition,url), this util will return a full router obj using a on the fly route definition, if the url match the definitionyou also get,srcsplat andparams, so you could without adding previous route definitions, make a one time check onsrc for even more flexibility.Think about it, in the first example i had to add another case for the more specific route (because i added it) is an artificial problem but will help to illustrate.

constComp=React.createClass({render(){consturl=this.props.router.url;constmatch=utils.match;if(match('/',url).src)return<Home/>//.src have a value with the url matches the definitionif(match('/foo/*',url).src)return<Foo/>return<NotFound/>}});

on Foo, we are not going to useutils.match instead we will useutils.check, match returns anobject with all those state things, you can use utils.check, it returns a boolean, if the only thingyou need is to check if the url matches a definition (that is our case on both components!)

constFoo=React.createClass({render(){consturl=this.props.router.url;constcheck=utils.check;if(check('/some',url))return<Foo/>if(check('foo/some/more/stuff/*',url))return<Stuff/>if(check('/some/more',url))return<Home/>return<NotFound/>}});

Understanding how react-tiny-router works

When the user enters a url on the browser, presses the back or forward buttons, or the navigateTo action creator is called,redux-tiny-router will dispatch an action:

{type:ROUTER_NAVIGATION,router:router...}

The router property already contains a populated router object, when this action reaches the router middleware, at the end of the middleware chain,it will read the action.router.url property and set the browser with that url, it will then reach the router reducer, that will make router part of the state.It's quite simple really, but now that you know this, it's easy to create a middleware to intercept this action.

let's make something cool here, if the user is going to a secure place in your app let's redirect him to /loginyou can see the full implementation inreact-redux-tiny example app.

inside your middleware..

if(action.type==='ROUTER_NAVIGATION'){const{url,path}=action.router;constisSecurePlace=utils.check('/secure/*',url);constloggedIn=getState().data.user;//presume that the data part of your state will hold the userif(path==='/login')//if user wants to login thats ok!returnnext(action);if(isSecurePlace&&!loggedIn){dispatch(tinyActions.preventedNavigationAttempted(url));//router will now store the attempted url (you can use this to send him where he wanted to go after auth)dispatch(tinyActions.navigateTo('/login'));//navigate to /loginreturn;// this will stop further ROUTER_NAVIGATION processing, the action it will never reach the router middleware or the reducer}returnnext(action);}//the rest of your middleware ....

In there, is business as usual, you could naturally dispatch your own actions with part of the router state,to your own part of the state and point your app there if you want, dispatch actions based on some part of therouter state to fetch some data, or whatever you need!. You can even do redirects differently, by callingutils.urlToRouter(url) you getnew router object based on the url you fed it, now place that on action.router (to replace it) and send it forwardnext(action)and you are done. You could of course just dispatch a navigateTo action and not return next(action) as we did on the example above,just showing how you can monkey around in your reducer, as this router works in a redux flow and it's just state, youhave plenty of opportunity to interact.

You could do your all your routing just by looking at the router or your "own" state, fetch data in the middleware or in your component,the router does not care...

The utils

the same utils the router uses you can use it tooimport {utils} from 'react-tiny-router';the ones are:

Returns a router object:

utils.urlToRouter('/some/cool/url?param=10&param2=nice')

Takes a path and a search object, returns a query string:

utils.toQueryString('/some/cool/url',{param:10,param2:'nice')//it will spill the url used above

Set a route definition

utils.set('/*')

Sets a bunch of route definitions

utils.setRoutes(['/*','/foo',]),

Returns a router object, also sets this router object with route definitions if it matches

utils.match('/foo',url);

Returns true if the url matches the definition false otherwise

utils.check('/foo',url);

Universal Apps

redux-tiny-router has a initUniversal function, that returns a promise, this promise resolves with data.html (with the rendered app)and data.state with your state, now just send those in, and presto, redux-tiny-router handles async on react just fine as long as allasync operations are done using actions, and that those actions ether return a promise or have an attribute that is a promise, you caneven load data on componentWillMount on react applications, you also don't need to wait or synchronize any async operations, as therouter will wait and re-render server side if on the first render, async actions where fired modifying the state.This makes the client not only receive the complete state of your app but also the final render from that state.

This example use a ejs template as it's quite elegant for this, or you could just use a react component

importcreateStorefrom'../your/path/create-store.js';//(this should return a function that creates a store)import{reduxTinyRouter}from'redux-tiny-router';importComponentfrom'../shared/components/Layout.jsx';reduxTinyRouter.initUniversal(url,createStore,Component).then((data)=>{res.render('index',{html:data.html,payload:JSON.stringify(data.state),});});

The ejs template:

<!DOCTYPE html><html><head><title>Redux Tiny Universal Example</title></head><body><divid="app"><%- html %></div><scripttype="text/javascript">window.__DATA__=<%-payload%>;</script><scripttype="text/javascript"src="/build/bundle.js"></script></body></html>

And on the client:

importReactfrom'react';importLayoutfrom'../shared/components/Layout.jsx';//your react appimportcreateStorefrom'../shared/redux/create-store.js';conststore=createStore(window.__DATA__,window.location.href);document.addEventListener('DOMContentLoaded',()=>{React.render(<Layoutstore={store}/>,document.getElementById('app'));});

And it works, the example universal appreact-redux-tiny can show you more!

Using client side OPTION2 (bring stuff by hand) if OPTION1 is robbing you of applyMiddleware form a third party or you combine your reducers in a fancy way

Create your store with the redux-tiny-router middleware and reducer

import{createStore,applyMiddleware,combineReducers}from'redux';import{tinyMiddleware,tinyReducer}from'redux-tiny-router';import*asyourReducersfrom'./reducers'letmiddleware=[appMiddleware,tinyMiddleware];//notice tinyMiddleware must be the last one;//middleware.unshift(tinyUniversal); //import tinyUniversal and uncomment if you are building a universal appvarreducer=combineReducers(Object.assign({},tinyReducer,yourReducers));varfinalCreateStore=applyMiddleware(...middleware)(createStore);store=finalCreateStore(reducer,{});

if you are building an universal app, you need to bringStandard stuff, for now you just added a middleware and a reducer from redux-tiny-router, you should turn this in to a functionthat returns the store that you can import for convenience and if you plan on doing an Universal app

Now you only have to call the init function with the store before you render your app:

import{reduxTinyRouter}from'redux-tiny-router';reduxTinyRouter.init(store);React.render(<Appstore={store}/>,document.getElementById('app'));...

DONE!

Inspired by cerebral reactive router

License

MIT

About

A Router made for Redux and made for universal apps! stop using the router as a controller... its just state!

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp