练手项目react-bookstore
react-bookstore线上地址:https://react-bookstore.herokuapp.com 学习了react相关技术,需要贯通一下。所以有了这个。会持续更新。项目不复杂,但我本来就是来练手的。我觉得达到了练手的效果。包括redux/react-redux的使用,以及使用redux-thunk处理异步请求,并且异步中还尝试使用了async/await。当然最重要的是实践了react-router。由于起步较晚,之前并没有使用了2.x,3.x的版本,所以看了v4的文档和官方demo后直接上手的。在浏览器中主要使用的是react-router-dom,很好的实践了其中的组件。包括其中注册登录的弹出框(而不是在新的页面)就是参照官方文档的。我看的是中文翻译后的文档,再次感谢翻译人员。除此之外,我还结合了github上react-router项目的文档这里关于某些部分的解释更为详细。
使用方法dev为开发分支,master为主分支(用于功能展示)。 git clone https://github.com/yuwanlin/react-bookstore.git
cd react-bookstore
yarn # or npm install
浏览器地址栏:
localhost:3000
react-router-dom中文文档 组件: API: 在浏览器环境下,这些知识足够了。 BrowserRouter
import { BrowserRouter } from 'react-router-dom'
<BrowserRouter basename={optionalString} forceRefresh={optionalBool} getUserConfirmation={optionalFunc} keyLength={optionalNumber} >
<App/>
</BrowserRouter>
在我的项目中 <Route component={App}/>
而不是直接的App组件。相同点是因为无论什么location,都会匹配到App组件,因为这里的Route没有明确的path。不同点是因为Route导航的组件的props中会默认有match、location、history参数。而我在App组件中就使用了location来判断模态框。 LinkLink是一个链接(实际上一个a标签),用来跳转到相应的路由。其中的 <Link to="/courses"/> <Link to={{ pathname: '/courses',search: '?sort=name',hash: '#the-hash',state: { fromDashboard: true } }}/>
其中pathname可以通过location.pathname或者match.URL得到。search可以通过location.search获取到。hash通过location.hash获取到。 Redirect表示重定向到一个新的页面。默认是使用新页面代替旧页面。如果加上 <Redirect path to='/courses'/>
其中 RouteRoute或许是最重要的组件了。它定义了路由对应的组件。它的 import { BrowserRouter as Router,Route } from 'react-router-dom'
<Router>
<div>
<Route exact path="/" component={Home}/>
<Route path="/news" component={NewsFeed}/>
</div>
</Router>
然后就是渲染对应组件的三种方式:
<Route path="/user/:username" component={User}/>
const User = ({ match }) => {
return <h1>Hello {match.params.username}!</h1>
}
// 便捷的行内渲染
<Route path="/home" render={() => <div>Home</div>}/>
// 包装/合成
const FadingRoute = ({ component: Component,...rest }) => (
<Route {...rest} render={props => (
<FadeIn>
<Component {...props}/>
</FadeIn>
)}/>
)
<FadingRoute path="/cool" component={Something}/>
警告:
<Route children={({ match,...rest }) => (
{/* Animate总会被渲染,所以你可以使用生命周期来使它的子组件出现
或者隐藏
*/}
<Animate>
{match && <Something {...rest}/>}
</Animate>
)}/>
警告: Switch无论如何,它最多只会渲染一个路由。或者是Route的,或者是Redirect的。考虑以下代码: <Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
如果Route不在Switch组件中,那么当URL是/about时,这三个路由组件都会渲染。其中第二个Route中,通过match.params.user可以获取URL–about。这种设计,允许我们以多种方式将多个 组合到我们的应用程序中,例如侧栏(sidebars),面包屑(breadcrumbs),bootstrap tabs等等。 然而,偶尔我们只想选择一个 <Switch>
<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
</Switch>
现在,只有第一个路由组件会渲染了。其缘由是Switch仅仅渲染一个路由。 对于当前地址(location),Route组件使用path匹配,Redirect组件使用from匹配。所以对于没有path的Route组件或者没有from的Redirect组件,总是可以匹配所有的地址。这一重大的应用当然就是404了。。 withRouter前面提到,使用Route组件匹配到的组件总是可以获得match、location、history属性。然后,对于一个普通的组件,有时也需要相关数据。这时,就可以使用到withRouter了。 const CommonComponent = ({match,location,history}) => null
const CommonComponent2 = withRouter(CommonComponent);
另外,有时候URL改变可能组件没有重载,这时因为组件可能检查到它的属性没有变。同理,使用withRoute,当URL改变,组件的this.props.location一定会改变,这样就可以使组件重载了。 match
地址栏: /user/real
<Route path="/user/:user">
此时:match如下:
{
params: { user: "real"}
isExact: true,path: "/user/:user",url: "user/real"
}
locationlocation 是指你当前的位置,下一步打算去的位置,或是你之前所在的位置,形式大概就像这样: 地址栏:/user/real?q=abc#sunny
{
key: 'ac3df4',// 在使用 hashHistory 时,没有key。值不一定
pathname: '/user/real'
search: '?q=abc',hash: '#sunny',state: undefined
}
在react-router中,可以在下列环境使用location。
通常,我们只需要使用字符串表示location,如下: <Link to="/user/:user">
使用对象形式可以表达更多的信息。如果需要从一个页面传递数据到另一个页面(除了URL相关数据),使用state是一个好方法。 <Link to={{ pathname: '/user/real',state: { data: 'your data'} }}>
我们也可以通过history.location获取location对象,但是不要这样做,因为history是可变的。而location是不可变的(URL发生变化location一定变化)。 class Comp extends React.Component {
componentWillReceiveProps(nextProps) {
// locationChanged 变量为 true
const locationChanged = nextProps.location !== this.props.location
// 不正确,locationChanged 变量会 *永远* 为 false ,因为 history 是可变的(mutable)。
const locationChanged = nextProps.history.location !== this.props.history.location
}
}
history
reactreact组件的生命周期中主要用到的有 renderrender方法自然不必多说,当组件的state或者props改变的时候组件会重新渲染。 componentDidMount这个方法在组件的声明周期中只会执行一次,这代表组件已经挂载了。所以在此方法中可以进行dom操作,异步请求(比如我用的dispatch,action中使用redux-thunk处理的异步请求)等。 componentWillReceiveProps这个方法也是常用的,它接受一个参数 componentWillUpdate这个方法接受两个参数,分别是 reduxredux提供的api主要有 applyMiddleware接受中间件。对于多个中间价,可以使用 const middlewares = []; applyMiddleware(...middlewares)
bindActionCreators这个函数自带dispatch。可能在mapDispatchToProps总会用到。 import actions as * from '../actions/index.js';
const mapDispatchToProps = (dispatch) => bindActionCreators(actions,dispatch);
compose有时候,项目中已经引入了一些middleware或别的store enhancer(applyMiddleware的结果本身就是store enhancer),如下: const store = createStore( reducer,preloadState,applyMiddleware(...middleware) )
这时候,需要将现有的enhancer与window.devToolsExtension()组合后传入,组合可以使用redux提供的辅助方法compose。 import { createStore,compose,applyMiddleware } from 'redux';
const store = createStore(
reducer,compose(
applyMiddleware(...middleware),window.devToolsExtension ? window.devToolsExtension() : f => f
)
)
compose的效果很简单:compose(a,b)的行为等价于(…args) => a(b(…args))。即从右向左执行,并将右边函数的返回值作为它左边函数的参数。如果 combineReducers将多个小的reducer合并成一个。由于redux中state只有一个,所以每个小的reducer都是state的一个属性。 createStore上面已经介绍过。 react-reduxreact-redux主要提供两个接口。 Provider顾名思义,Provider的主要作用是“provide”。Provider的角色是store的提供者。一般情况下,把原有组件树根节点包裹在Provider中,这样整个组件树上的节点都可以通过connect获取store。 <Provider store={store}>
<App />
</Provider>
connectconnect用来“连接”组件与store。它的形式如下: connect([mapStateToProps],[mapDispatchToProps],[mergeProps],[options])
部署项目地址:https://react-bookstore.herokuapp.com
目前已完成的功能1.路由和搜索 2.分页 5月9日更新(接下来这个项目不知道往哪写了,有没有老铁们star一下)3.注册和登录功能 注册:在reduces中使用state.user.users保存用户的用户名。注册时做了验证。 登录:查看state.user.users查看是否匹配。 5月14日更新4.商品详情页 5.添加到购物车 6.查看购物车 感觉达到了练手的效果,剩下的就没写了。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |