关于react-router4:reactrouter

晚期是凭借多个页面借助a标签实现路由的跳转,也就是多页面编程,n个导航对应n个html.所以spa 应运而生。单页面多组件 路由的工作原理【一种映射关系 门路--组件】 一开始的地址是没有参数的点击路由连贯后,页面并不会产生跳转。并且路由会批改浏览器的地址路由中检测浏览器的门路(参数)变动(这里是会疏忽后面的ip 以及port)匹配组件参数,展现相应的组件路由监测的实现: 得益于history对象 浏览器历史记录 咱们个别不会间接去操作这个window.history 去操作浏览器的门路 历史记录的批改以及替换 因为原生的api不好用 so借助一个库history.js 这个创立的history对象实质也是操作了bom的historyhistory.push/forward/go/back/replace history: HASH(# 兼容性好) browser react-router 库其实有三种实现 实用于三种平台 web(dom) native(原生) anywhere(api 过多) router: 路由器 治理路由route: 路由(接口 天线) LINK ROUTE 组件须要包裹在路由器Router内 BrowseRouter 或 HashRouter 必须被路由器所治理 而且只能是一个 多个路由器之间是独立的link 中的to的属性值最好是小写的 因为不辨别大小写路由链接link 实质是a标签 作用就是点击后批改地址栏门路 浏览器的历史记录就会工作 当门路变动就会匹配组件1) tnpm i react-router-dom 原生写react 简单须要多个html 引入各种react文件 编码慢通过js批改为jsx的模式依附浏览器babel 翻译 代码过多浏览器忙不过来应用cli 简略疾速搭建react 利用 cli是webpeck 搭建的,他有好多loader plugins 由很多性能‘装置脚手架 create-react-app库帮忙你装置cli创立我的项目 create-react-app 我的项目名启动 npm startwebpeck 官网把对于react的配置文件暗藏了 执行eject 会裸露处所有的配置文件并且回不去了cli: 网络申请。react中不倡议应用jquery 因为他重视的是操作原生dom 而 react是不关注视图层的axios: Q轻量 可运行在服务端 反对promise 实质就是xmlHttpRequest对象的封装全局装置后援用报错 ...

December 3, 2021 · 1 min · jiezi

React入门从项目搭建webpack到引入路由reactrouter和状态管理reactredux

一、什么是ReactReact是什么?React的官网是这样介绍的:React-用于构建用户界面的 JavaScript 库。看起来十分简洁,React仅仅是想提供一个构建用户界面的工具,也就是只关心UI层面,不关心数据层面,数据层面的东西交给专门的人(react-redux)来做。所以有些人会说React就是一个ui框架。React 认为渲染逻辑本质上与其他 UI 逻辑内在耦合,比如,在 UI 中需要绑定处理事件、在某些时刻状态发生变化时需要通知到 UI,以及需要在 UI 中展示准备好的数据。所以,React提供了jsx语法,也可以理解为让你在ul组件里写很多东西。jsx最终是通过babel将组件转化为React.createElement()函数来调用。组件化开发的乐趣需要慢慢体会。既然是入门,下面通过搭建项目-小例子-引入路由/状态管理简单介绍下react,有关复杂的概念这里暂不提及。 const name = 'Josh Perez';const element = <h1>Hello, {name}</h1>;ReactDOM.render( element, document.getElementById('root'));二、项目搭建上面说了,react的组件开发是需要babel进行编译的,浏览器是不能直接解析的,所以react项目一般都是需要做一些简单的配置的。当然,有很多的配置完善的脚手架可以使用,比如官方脚手架creat-react-app或者蚂蚁金服的dva框架,使用起来都是很方便上手。但是咱们我觉得通过简单搭建一个项目可以更好的理解react。下面将会通过webpack一步一步搭建一个可以使用的项目,但是具体的优化这里就先不考虑了。 1.打开终端、创建项目 mkdir react-demo && cd react-demo //创建文件夹,并进入文件夹2.初始化构建 npm init //为了后面下载npm包和node配置使用 //一路回车键就可以了!项目中多出来一个package.json文件3.创建项目入口 新建app.js文件,引入react和react-dom,新建一个index.html,包含<div id='app'></div>。 import React from 'react'; // 终端执行 npm i react 下载react import ReactDom from 'react-dom' // 终端执行 npm i react-dom 下载react-dom function App(){ //以函数的方式创建react组件 return <div>welcom,react-app</div> } ReactDom.render( //将组件App渲染挂载到页面的根节点app <App />, document.getElementById('app')//所以需要新建一个html文件提供app节点供组件挂载 )3.webpack配置 ...

August 27, 2019 · 2 min · jiezi

在redux中使用reactrouterredux-跳转路由

1.安装npm install --save historynpm install --save react-router-redux2.封装import {createStore, compose, applyMiddleware} from 'redux';import thunk from 'redux-thunk';import reducer from './reducer';import {routerMiddleware} from 'react-router-redux';let createHistory = require('history').createHashHistory;let history = createHistory(); // 初始化historylet routerWare = routerMiddleware(history);const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;const store = createStore(reducer, composeEnhancers( applyMiddleware(thunk, routerWare)));export default store;3.使用import {push} from 'react-router-redux';// 任意一个actionCreators.js文件// 登录export const loginSystem = (params) => async (dispatch) => { try { dispatch(changeLoading(true)); let {data} = await loginAsync(params); if (data['msgCode'] === 0) { dispatch(changeUserName(true, params['username'])); dispatch(push('/home')); // 跳转到home页面,其它都是示例代码,可忽略 } else { Toast.info(data['message'], 2); } dispatch(changeLoading(false)); } catch (error) { Toast.info(error, 2); }}

August 7, 2019 · 1 min · jiezi

React脚手架搭建

前言之前的 multi-spa-webpack-cli 只是为 React + antd 模板提供了开发时必要的环境,对于实际的开发并没有什么用处。为了更贴近实际开发,本次 React + antd 模板完善了一些功能。 封装 fetch,新增请求错误提示;集成 react-router-dom 路由管理;集成 react-redux 状态管理;必不可少的 antd 集成;node 服务集成(可选)。node 服务和 React+antd 模板是没有多大的关系的。本文只是想通过一个简单的登陆逻辑来演示以上的功能,所以 node 服务不是必须的。 multi-spa-webpack-cli 已经发布到 npm,只要在 node 环境下安装即可。 npm install multi-spa-webpack-cli -g使用步骤如下: # 1. 初始化项目multi-spa-webpack-cli init spa-project<center> </center> # 2. 进入文件目录cd spa-project# 3. 安装依赖npm install# 4. 打包不变的部分npm run build:dll# 5. 启动项目(手动打开浏览器:localhost:8090)npm start# 6. 启动服务(可选)cd servernpm installnpm start预览: <center> </center> 封装 fetch现在处理异步的方式,大多数基于 Promise 的。而 fetch 是天然支持 Promise 的,所以无需再手动封装。在 PWA 技术中,已作为一个重要的组成部分在使用。 ...

July 1, 2019 · 2 min · jiezi

React搭建个人博客一项目简介与React前端踩坑

一.简介项目最开始目的是为了熟悉React用法,之后加入的东西变多,这里分成三部分,三篇博客拆开来讲。 前端部分 [x] React[x] React-Router4.0[x] Redux[x] AntDesign[x] webpack4后端部分 [x] consul+consul-template+nginx+docker搭建微服务[x] cdn上传静态资源[x] thinkJs运维部分 [x] daocloud自动化部署[x] Prometheus+Grafana监控系统博客网站分为两个部分,前台博客展示页面,后台管理页面。先看下效果:前台页面:也可以看我线上正在用的博客前台,点这里后台页面: 这一篇只讲前端部分功能描述 [x] 文章列表展示[x] 登录管理[x] 文章详情页展示[x] 后台文章管理[x] 后台标签管理[x] MarkDown发文项目结构前后台页面项目结构类似,都分为前端项目和后端项目两个部分。前端项目开发环境使用webpack的devserver,单独启动一个服务,访问后端服务的接口。生产环境直接打包到后端项目指定目录,线上只启动后端服务。前台页面:前端项目代码地址 在这里。后端项目代码地址在这里。后台页面:前端项目代码地址 在这里。后端项目代码地址在这里。 二.React踩坑记录这里讲一下项目中React踩坑和一些值得留意的问题。 1.启动报错如下:下图是一开始blog-react项目一个报错因为项目里我使用webpack来运行项目,以及添加相关配置,而不是刚建好项目时的react命令,所以一开始启动的时候会报 Module build failed: SyntaxError: Unexpected token 错误。说明ReactDom.render这句话无法解析。解决方法,添加babel的react-app预设,我直接加到了package.json里。 "scripts": { "start": "cross-env NODE_ENV=development webpack-dev-server --mode development --inline --progress --config build/webpack.config.dev.js", "build": "cross-env NODE_ENV=production webpack --env=test --progress --config ./build/webpack.config.prod.js", "test": "react-scripts test", "eject": "react-scripts eject" }, "babel": { "presets": [ "react-app" ] },2.React 组件方法绑定this问题React跟Vue不一样的一点是,组件里定义的方法,如果直接调用会报错。比如: ...

June 19, 2019 · 5 min · jiezi

路由

June 14, 2019 · 0 min · jiezi

reactrouter40-使用方法和源码分析

react-router-dom@4.3.0 || react-router@4.4.1react-router 使用方法配置 router.js import React, { Component } from 'react';import { Switch, Route } from 'react-router-dom';const router = [{ path: '/', exact: true, component:importPath({ loader: () => import(/* webpackChunkName:"home" */ "pages/home/index.js"), }), },]const Routers = () => ( <main> <Switch> { router.map(({component,path,exact},index)=>{ return <Route exact={exact} path={path} component={component} key={path} /> }) } </Switch> </main>);export default Routers;入口 index.js import {HashRouter} from 'react-router-dom';import React from 'react';import ReactDOM from 'react-dom';import Routers from './router';ReactDOM.render ( <HashRouter> <Routers /> </HashRouter>, document.getElementById ('App'));home.js ...

June 3, 2019 · 4 min · jiezi

reactrouterdom-4-基础api

BrowserRouter<BrowserRouter> 使用 HTML5 提供的 history API (pushState, replaceState 和 popstate 事件) 来保持 UI 和 URL 的同步。 <BrowserRouter basename={string} forceRefresh={bool} getUserConfirmation={func} keyLength={number}>basename: string所有位置的基准 URL。如果你的应用程序部署在服务器的子目录,则需要将其设置为子目录。basename 的正确格式是前面有一个前导斜杠,但不能有尾部斜杠。例子: <BrowserRouter basename="/calendar"> <Link to="/today" /></BrowserRouter>效果: <a href="/calendar/today" />forceRefresh: bool如果为 true ,在导航的过程中整个页面将会刷新。一般情况下,只有在不支持 HTML5 history API 的浏览器中使用此功能。例子: const supportsHistory = 'pushState' in window.history;<BrowserRouter forceRefresh={!supportsHistory} />getUserConfirmation: func用于确认导航的函数,默认使用 window.confirm。例如,当从 /a 导航至 /b 时,会使用默认的 confirm 函数弹出一个提示,用户点击确定后才进行导航,否则不做任何处理。译注:需要配合 <Prompt> 一起使用。 // 这是默认的确认函数const getConfirmation = (message, callback) => { const allowTransition = window.confirm(message); callback(allowTransition);}<BrowserRouter getUserConfirmation={getConfirmation} />KeyLength:数字,设置Location.Key的长度; ...

May 14, 2019 · 4 min · jiezi

reactrouter中的exact和strict

前言每次用配置react路由都会考虑是否应该给给<Route>组件加上exact或strict。下面妹子将于自认为比较清晰的方式列举出来什么场景需要加和不加。 本文案例主要以react-router v4+为主,v5版本是因为发布时版本依赖有问题而直接跳成这个大版本的,用法和4完全相同,就是这么任性 > < ,升级详情可看本文最后链接exactexact默认为false,如果为true时,需要和路由相同时才能匹配,但是如果有斜杠也是可以匹配上的。如果在父路由中加了exact,是不能匹配子路由的,建议在子路由中加exact,如下所示 //父路由<Switch> <Route path="/a" component={ComponentA} /></Switch>//子路由,tuanDetail组件里<Switch> <Route path="/a/b" exact component={ComponentB}/></Switch>strict<Route strict path="/one" component={About} /> strict默认为false,如果为true时,路由后面有斜杠而url中没有斜杠,是不匹配的 案例 总结如果没有子路由的情况,建议大家配都加一个exact;如果有子路由,建议在子路由中加exact,父路由不加;而strict是针对是否有斜杠的,一般可以忽略不配置。 其他链接原文地址:https://raoenhui.github.io/react/2019/05/04/exact-strict/https://reacttraining.com/react-router/web/api/Route/exact-boolhttps://reacttraining.com/blog/react-router-v5/Happy coding .. :)

May 10, 2019 · 1 min · jiezi

react离开页面自定义弹框拦截路由拦截

前言:项目有个需求是:跳转路由,在离开页面前,需要弹框询问用户是否确定离开。用react-router的<Prompt>组件是可以的,但是,怎么使用antd组件(或者说自定义组件)呢?请看下面 先看的这个:https://stackoverflow.com/questions/32841757/detecting-user-leaving-page-with-react-router(1)使用react-router的<Prompt> import { Prompt } from 'react-router'<Prompt message="你确定要离开嘛?"/> (2)<Prompt>支持函数 <Prompt when={true} message={(location) => { return window.confirm(`confirm to leave to ${location.pathname}?`); }}/> (3)history.block,这个是个坑! import { history } from 'path/to/history';class MyComponent extends React.Component { componentDidMount() { history.block(targetLocation => { // take your action here return false; }); } render() { //component render here }}坑的原因是history.block()方法是不能和<Modal>组件并列使用的,而必须在history.block()内部去调用<Modal>组件(就是注释take your action here那里),这就导致一个问题:<Modal>组件里的 onOk、onCancel 如何返回值给 history.block()的 return 语句(return false/true)的同时,不让history.block()的 return 语句和 <Modal>组件顺序执行呢? 说白点就是,<Modal>组件先显示出来,阻塞后面的 return false/true,等 <Modal>组件的 onOk、onCancel 方法执行后,再 return false/true ...

May 8, 2019 · 1 min · jiezi

七分设计感的纯React项目Mung

多语言版本React版MungReact-Native版MungFlutter版Mung1. Mung:是一个基于React编写,使用豆瓣a开源API开发的一个项目。 2. 功能概述数据保存 :支持断网加载缓存数据。主题换肤 :现在只支持切换主题颜色,本项目没几张图片。查看电影详情 :支持查看电影详情包括评论。一键搜索: 支持标签和语句查找相关的电影。3. 运行结果图 4. 使用到的框架antd-mobile :阿里的UI库,主要用到里面列表显示、Toast提醒。Mobx :实现状态管理。react-loading :加载进度条。react-router-dom :路由管理。react-transition-group :实现动画效果。SCSS: 样式编写方便、清晰。5. 总结这是一个React项目主要,之前写过一个纯React-Native项目Mung没有使用状态管理库和UI库(除渐变库),相比React-Native,现在React水平还是比较一般,尤其是webpack、babel配置等方面,后续有时间还得多看看。里面还有一个ant-design按需加载的问题,使用react-app-rewired配置后和scss发生冲突,有时间再改下。 6. GitHub地址

April 28, 2019 · 1 min · jiezi

React-Router-v4教程为你的-React-应用创建路由

翻译:疯狂的技术宅原文:https://www.edureka.co/blog/r...本文首发微信公众号:前端先锋欢迎关注,每天都给你推送新鲜的前端技术文章 在这篇关于 React Router 的博文中,我将引导你搞懂 React 中路由的概念。 你将看到以下主题: 常规路由为什么需要 React 路由?React 中的路由React Router v4 的优点常规路由通常,当用户在浏览器中键入 URL 时,会向服务器发送 HTTP 请求,然后服务器检索 HTML 页面。对于每个新URL,用户会被重定向到新的 HTML 页面。你可以通过参考下图来更好地理解路由的工作原理。 为什么需要 React 路由?将单页应用限制为单一视图并不适用于 Facebook、Instagram 等流行的社交媒体网站,这些网站现在使用 React 呈现多个视图。我们需要继续前进,学习如何在单页面应用中显示多个视图。 例如我们习惯看到显示欢迎消息和相关内容的主页。网站介绍的详细信息可以在“关于我们”页面上找到,用户列表及其详细信息会出现在不同的页面上,可能还有其他各种页面包含很多不同的视图。 那么你认为这是怎样实现的呢? 在程序中添加路由器可以解决这一需求。 React 中的路由这将把我们带到本文的主题:React Router v4。 2017年3月13日,Micheal Jackson 和 Ryan Florence 发布了React Router v4 及可靠的文档。 他们相信“Learn Once, Route Anywhere”的理念。 在 React Conf 2017 的演讲中,他们通过展示如何将路由概念无缝地从 Web 平台投射到 Native 平台,以及将 React Router 集成到 VR 并在 React Native 中创建动画来解释这一点。虽然他们的谈话中的着眼点是围绕路由器 API 是如何“All About Components”的。 ...

April 23, 2019 · 2 min · jiezi

一次react-router + react-transition-group实现转场动画的探索

原文地址1. Introduction在日常开发中,页面切换时的转场动画是比较基础的一个场景。在react项目当中,我们一般都会选用react-router来管理路由,但是react-router却并没有提供相应的转场动画功能,而是非常生硬的直接替换掉组件。一定程度上来说,体验并不是那么友好。为了在react中实现动画效果,其实我们有很多的选择,比如:react-transition-group,react-motion,Animated等等。但是,由于react-transition-group给元素添加的enter,enter-active,exit,exit-active这一系列勾子,简直就是为我们的页面入场离场而设计的。基于此,本文选择react-transition-group来实现动画效果。接下来,本文就将结合两者提供一个实现路由转场动画的思路,权当抛砖引玉~2. Requirements我们先明确要完成的转场动画是什么效果。如下图所示:3. react-router首先,我们先简要介绍下react-router的基本用法(详细看官网介绍)。这里我们会用到react-router提供的BrowserRouter,Switch,Route三个组件。BrowserRouter:以html5提供的history api形式实现的路由(还有一种hash形式实现的路由)。Switch:多个Route组件同时匹配时,默认都会显示,但是被Switch包裹起来的Route组件只会显示第一个被匹配上的路由。Route:路由组件,path指定匹配的路由,component指定路由匹配时展示的组件。// src/App1/index.jsexport default class App1 extends React.PureComponent { render() { return ( <BrowserRouter> <Switch> <Route exact path={’/’} component={HomePage}/> <Route exact path={’/about’} component={AboutPage}/> <Route exact path={’/list’} component={ListPage}/> <Route exact path={’/detail’} component={DetailPage}/> </Switch> </BrowserRouter> ); }}如上所示,这是路由关键的实现部分。我们一共创建了首页,关于页,列表页,详情页这四个页面。跳转关系为:首页 ↔ 关于页首页 ↔ 列表页 ↔ 详情页来看下目前默认的路由切换效果:4. react-transition-group从上面的效果图中,我们可以看到react-router在路由切换时完全没有过渡效果,而是直接替换的,显得非常生硬。正所谓工欲善其事,必先利其器,在介绍实现转场动画之前,我们得先学习如何使用react-transition-group。基于此,接下来就将对其提供的CSSTransition和TransitionGroup这两个组件展开简要介绍。4.1 CSSTransitionCSSTransition是react-transition-group提供的一个组件,这里简单介绍下其工作原理。When the in prop is set to true, the child component will first receive the class example-enter, then the example-enter-active will be added in the next tick. CSSTransition forces a reflow between before adding the example-enter-active. This is an important trick because it allows us to transition between example-enter and example-enter-active even though they were added immediately one after another. Most notably, this is what makes it possible for us to animate appearance.这是来自官网上的一段描述,意思是当CSSTransition的in属性置为true时,CSSTransition首先会给其子组件加上xxx-enter的class,然后在下个tick时马上加上xxx-enter-active的class。所以我们可以利用这一点,通过css的transition属性,让元素在两个状态之间平滑过渡,从而得到相应的动画效果。相反地,当in属性置为false时,CSSTransition会给子组件加上xxx-exit和xxx-exit-active的class。(更多详细介绍可以戳官网查看)基于以上两点,我们是不是只要事先写好class对应的css样式即可?可以做个小demo试试,如下代码所示:// src/App2/index.jsexport default class App2 extends React.PureComponent { state = {show: true}; onToggle = () => this.setState({show: !this.state.show}); render() { const {show} = this.state; return ( <div className={‘container’}> <div className={‘square-wrapper’}> <CSSTransition in={show} timeout={500} classNames={‘fade’} unmountOnExit={true} > <div className={‘square’} /> </CSSTransition> </div> <Button onClick={this.onToggle}>toggle</Button> </div> ); }}/* src/App2/index.css /.fade-enter { opacity: 0; transform: translateX(100%);}.fade-enter-active { opacity: 1; transform: translateX(0); transition: all 500ms;}.fade-exit { opacity: 1; transform: translateX(0);}.fade-exit-active { opacity: 0; transform: translateX(-100%); transition: all 500ms;}来看看效果,是不是和页面的入场离场效果有点相似?<div align=“center”> <img src=“https://upload-images.jianshu…;/></div>4.2 TransitionGroup用CSSTransition来处理动画固然很方便,但是直接用来管理多个页面的动画还是略显单薄。为此我们再来介绍react-transition-group提供的TransitionGroup这个组件。The <TransitionGroup> component manages a set of transition components (<Transition> and <CSSTransition>) in a list. Like with the transition components, <TransitionGroup> is a state machine for managing the mounting and unmounting of components over time.如官网介绍,TransitionGroup组件就是用来管理一堆节点mounting和unmounting过程的组件,非常适合处理我们这里多个页面的情况。这么介绍似乎有点难懂,那就让我们来看段代码,解释下TransitionGroup的工作原理。// src/App3/index.jsexport default class App3 extends React.PureComponent { state = {num: 0}; onToggle = () => this.setState({num: (this.state.num + 1) % 2}); render() { const {num} = this.state; return ( <div className={‘container’}> <TransitionGroup className={‘square-wrapper’}> <CSSTransition key={num} timeout={500} classNames={‘fade’} > <div className={‘square’}>{num}</div> </CSSTransition> </TransitionGroup> <Button onClick={this.onToggle}>toggle</Button> </div> ); }}我们先来看效果,然后再做解释:对比App3和App2的代码,我们可以发现这次CSSTransition没有in属性了,而是用到了key属性。但是为什么仍然可以正常工作呢?在回答这个问题之前,我们先来思考一个问题:由于react的dom diff机制用到了key属性,如果前后两次key不同,react会卸载旧节点,挂载新节点。那么在上面的代码中,由于key变了,旧节点难道不是应该立马消失,但是为什么我们还能看到它淡出的动画过程呢?关键就出在TransitionGroup身上,因为它在感知到其children变化时,会先保存住即将要被移除的节点,而在其动画结束时才会真正移除该节点。所以在上面的例子中,当我们按下toggle按钮时,变化的过程可以这样理解:<TransitionGroup> <div>0</div></TransitionGroup>⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️<TransitionGroup> <div>0</div> <div>1</div></TransitionGroup>⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️<TransitionGroup> <div>1</div></TransitionGroup>如上所解释,我们完全可以巧妙地借用key值的变化来让TransitionGroup来接管我们在过渡时的页面创建和销毁工作,而仅仅需要关注如何选择合适的key值和需要什么样css样式来实现动画效果就可以了。5. Page transition animation基于前文对react-router和react-transition-group的介绍,我们已经掌握了基础,接下来就可以将两者结合起来做页面切换的转场动画了。在上一小节的末尾有提到,用了TransitionGroup之后我们的问题变成如何选择合适的key值。那么在路由系统中,什么作为key值比较合适呢?既然我们是在页面切换的时候触发转场动画,自然是跟路由相关的值作为key值合适了。而react-router中的location对象就有一个key属性,它会随着浏览器中的地址发生变化而变化。然而,在实际场景中似乎并不适合,因为query参数或者hash变化也会导致location.key发生变化,但往往这些场景下并不需要触发转场动画。因此,个人觉得key值的选取还是得根据不同的项目而视。大部分情况下,还是推荐用location.pathname作为key值比较合适,因为它恰是我们不同页面的路由。说了这么多,还是看看具体的代码是如何将react-transition-group应用到react-router上的吧:// src/App4/index.jsconst Routes = withRouter(({location}) => ( <TransitionGroup className={‘router-wrapper’}> <CSSTransition timeout={5000} classNames={‘fade’} key={location.pathname} > <Switch location={location}> <Route exact path={’/’} component={HomePage} /> <Route exact path={’/about’} component={AboutPage} /> <Route exact path={’/list’} component={ListPage} /> <Route exact path={’/detail’} component={DetailPage} /> </Switch> </CSSTransition> </TransitionGroup>));export default class App4 extends React.PureComponent { render() { return ( <BrowserRouter> <Routes/> </BrowserRouter> ); }}这是效果:App4的代码思路跟App3大致相同,只是将原来的div换成了Switch组件,而且还用到了withRouter。withRouter是react-router提供的一个高阶组件,可以为你的组件提供location,history等对象。因为我们这里要用location.pathname作为CSSTransition的key值,所以用到了它。另外,这里有一个坑,就是Switch的location属性。A location object to be used for matching children elements instead of the current history location (usually the current browser URL).这是官网中的描述,意思就是Switch组件会用这个对象来匹配其children中的路由,而且默认用的就是当前浏览器的url。如果在上面的例子中我们不给它指定,那么在转场动画中会发生很奇怪的现象,就是同时有两个相同的节点在移动。。。就像下面这样:这是因为TransitionGroup组件虽然会保留即将被remove的Switch节点,但是当location变化时,旧的Switch节点会用变化后的location去匹配其children中的路由。由于location都是最新的,所以两个Switch匹配出来的页面是相同的。好在我们可以改变Switch的location属性,如上述代码所示,这样它就不会总是用当前的location匹配了。6. Page dynamic transition animation虽然前文用react-transition-group和react-router实现了一个简单的转场动画,但是却存在一个严重的问题。仔细观察上一小节的示意图,不难发现我们的进入下个页面的动画效果是符合预期的,但是后退的动画效果是什么鬼。。。明明应该是上个页面从左侧淡入,当前页面从右侧淡出。但是为什么却变成当前页面从左侧淡出,下个页面从右侧淡入,跟进入下个页面的效果是一样的。其实错误的原因很简单:首先,我们把路由改变分成forward和back两种操作。在forward操作时,当前页面的exit效果是向左淡出;在back操作时,当前页面的exit效果是向右淡出。所以我们只用fade-exit和fade-exit-active这两个class,很显然,得到的动画效果肯定是一致的。因此,解决方案也很简单,我们用两套class来分别管理forward和back操作时的动画效果就可以了。/ src/App5/index.css // 路由前进时的入场/离场动画 /.forward-enter { opacity: 0; transform: translateX(100%);}.forward-enter-active { opacity: 1; transform: translateX(0); transition: all 500ms;}.forward-exit { opacity: 1; transform: translateX(0);}.forward-exit-active { opacity: 0; transform: translateX(-100%); transition: all 500ms;}/ 路由后退时的入场/离场动画 */.back-enter { opacity: 0; transform: translateX(-100%);}.back-enter-active { opacity: 1; transform: translateX(0); transition: all 500ms;}.back-exit { opacity: 1; transform: translateX(0);}.back-exit-active { opacity: 0; transform: translate(100%); transition: all 500ms;}不过光有css的支持还不行,我们还得在不同的路由操作时加上合适的class才行。那么问题又来了,在TransitionGroup的管理下,一旦某个组件挂载后,其exit动画其实就已经确定了,可以看官网上的这个issue。也就是说,就算我们动态地给CSSTransition添加不同的ClassNames属性来指定动画效果,但其实是无效的。解决方案其实在那个issue的下面就给出了,我们可以借助TransitionGroup的ChildFactory属性以及React.cloneElement方法来强行覆盖其className。比如:<TransitionGroup childFactory={child => React.cloneElement(child, { classNames: ‘your-animation-class-name’})}> <CSSTransition> … </CSSTransition></TransitionGroup>上述几个问题都解决之后,剩下的问题就是如何选择合适的动画class了。而这个问题的实质在于如何判断当前路由的改变是forward还是back操作了。好在react-router已经贴心地给我们准备好了,其提供的history对象有一个action属性,代表当前路由改变的类型,其值是’PUSH’ | ‘POP’ | ‘REPLACE’。所以,我们再调整下代码:// src/App5/index.jsconst ANIMATION_MAP = { PUSH: ‘forward’, POP: ‘back’}const Routes = withRouter(({location, history}) => ( <TransitionGroup className={‘router-wrapper’} childFactory={child => React.cloneElement( child, {classNames: ANIMATION_MAP[history.action]} )} > <CSSTransition timeout={500} key={location.pathname} > <Switch location={location}> <Route exact path={’/’} component={HomePage} /> <Route exact path={’/about’} component={AboutPage} /> <Route exact path={’/list’} component={ListPage} /> <Route exact path={’/detail’} component={DetailPage} /> </Switch> </CSSTransition> </TransitionGroup>));再来看下修改之后的动画效果:7. Optimize其实,本节的内容算不上优化,转场动画的思路到这里基本上已经结束了,你可以脑洞大开,通过添加css来实现更炫酷的转场动画。不过,这里还是想再讲下如何将我们的路由写得更配置化(个人喜好,不喜勿喷)。我们知道,react-router在升级v4的时候,做了一次大改版。更加推崇动态路由,而非静态路由。不过具体问题具体分析,在一些项目中个人还是喜欢将路由集中化管理,就上面的例子而言希望能有一个RouteConfig,就像下面这样:// src/App6/RouteConfig.jsexport const RouterConfig = [ { path: ‘/’, component: HomePage }, { path: ‘/about’, component: AboutPage, sceneConfig: { enter: ‘from-bottom’, exit: ’to-bottom’ } }, { path: ‘/list’, component: ListPage, sceneConfig: { enter: ‘from-right’, exit: ’to-right’ } }, { path: ‘/detail’, component: DetailPage, sceneConfig: { enter: ‘from-right’, exit: ’to-right’ } }];透过上面的RouterConfig,我们可以清晰的知道每个页面所对应的组件是哪个,而且还可以知道其转场动画效果是什么,比如关于页面是从底部进入页面的,列表页和详情页都是从右侧进入页面的。总而言之,我们通过这个静态路由配置表可以直接获取到很多有用的信息,而不需要深入到代码中去获取信息。那么,对于上面的这个需求,我们对应的路由代码需要如何调整呢?请看下面:// src/App6/index.jsconst DEFAULT_SCENE_CONFIG = { enter: ‘from-right’, exit: ’to-exit’};const getSceneConfig = location => { const matchedRoute = RouterConfig.find(config => new RegExp(^${config.path}$).test(location.pathname)); return (matchedRoute && matchedRoute.sceneConfig) || DEFAULT_SCENE_CONFIG;};let oldLocation = null;const Routes = withRouter(({location, history}) => { // 转场动画应该都是采用当前页面的sceneConfig,所以: // push操作时,用新location匹配的路由sceneConfig // pop操作时,用旧location匹配的路由sceneConfig let classNames = ‘’; if(history.action === ‘PUSH’) { classNames = ‘forward-’ + getSceneConfig(location).enter; } else if(history.action === ‘POP’ && oldLocation) { classNames = ‘back-’ + getSceneConfig(oldLocation).exit; } // 更新旧location oldLocation = location; return ( <TransitionGroup className={‘router-wrapper’} childFactory={child => React.cloneElement(child, {classNames})} > <CSSTransition timeout={500} key={location.pathname}> <Switch location={location}> {RouterConfig.map((config, index) => ( <Route exact key={index} {…config}/> ))} </Switch> </CSSTransition> </TransitionGroup> );});由于css代码有点多,这里就不贴了,不过无非就是相应的转场动画配置,完整的代码可以看github上的仓库。我们来看下目前的效果:8. Summarize本文先简单介绍了react-router和react-transition-group的基本使用方法;其中还分析了利用CSSTransition和TransitionGroup制作动画的工作原理;接着又将react-router和react-transition-group两者结合在一起完成一次转场动画的尝试;并利用TransitionGroup的childFactory属性解决了动态转场动画的问题;最后将路由配置化,实现路由的统一管理以及动画的配置化,完成一次react-router + react-transition-group实现转场动画的探索。9. ReferenceA shallow dive into router v4 animated transitionsDynamic transitions with react router and react transition groupIssue#182 of react-transition-groupStackOverflow: react-transition-group and react clone element do not send updated props本文所有代码托管在这儿,如果觉得不错的,可以给个star。 ...

April 14, 2019 · 4 min · jiezi

# react-router v4 刷新出现找不到页面(NO FOUND)解决方案

react-router v4 刷新页面出现找不到问题解决方案原因### 浏览器被刷新相当于重新请求了服务端的 page 接口,当后端没有这个接口时,就没有document文档返回,这时url 并没有被js 观察处理解决如果是使用webpack-dev-server,请将 historyApiFallback: true 这个配置加入至 devServer 中. 以及在output 中配置 publicPath: ‘/‘如果是使用自定义的node服务器的话,需自己手写一个404接口. 将所有的url 都返回到index.html文档实例koaconst koaWebpack = require(‘koa-webpack’);async startService() {const middleware = await koaWebpack({ config: this.webpackConfig });this.app.use(middleware);app.use(async ctx => { const filename = path.resolve(this.webpackConfig.output.path, ‘index.html’); ctx.response.type = ‘html’; ctx.response.body = middleware.devMiddleware.fileSystem.createReadStream( filename );});this.app.listen(this.port, () => { console.log(当前服务器已启动, http://${this.host}:${this.port});});}[参考地址][1]

March 31, 2019 · 1 min · jiezi

React 服务端渲染从入门到精通

前言这篇文章是我自己在搭建个人网站的过程中,用到了服务端渲染,看了一些教程,踩了一些坑。想把这个过程分享出来。我会尽力把每个步骤讲明白,将我理解的全部讲出来。文中的示例代码来自于这个仓库,也是我正在搭建的个人网站,大家可以一起交流一下。本文中用到的技术React V16 | React-Router v4 | Redux | Redux-thunk | expressReact 服务端渲染服务端渲染的基本套路就是用户请求过来的时候,在服务端生成一个我们希望看到的网页内容的HTML字符串,返回给浏览器去展示。浏览器拿到了这个HTML之后,渲染出页面,但是并没有事件交互,这时候浏览器发现HTML中加载了一些js文件(也就是浏览器端渲染的js),就直接去加载。加载好并执行完以后,事件就会被绑定上了。这时候页面被浏览器端接管了。也就是到了我们熟悉的js渲染页面的过程。需要实现的目标:React组件服务端渲染路由的服务端渲染保证服务端和浏览器的数据唯一css的服务端渲染(样式直出)一般的渲染方式服务端渲染:服务端生成html字符串,发送给浏览器进行渲染。浏览器端渲染:服务端返回空的html文件,内部加载js完全由js与css,由js完成页面的渲染优点与缺点服务端渲染解决了首屏加载速度慢以及seo不友好的缺点(Google已经可以检索到浏览器渲染的网页,但不是所有搜索引擎都可以)但增加了项目的复杂程度,提高维护成本。如果非必须,尽量不要用服务端渲染整体思路需要两个端:服务端、浏览器端(浏览器渲染的部分)第一: 打包浏览器端代码第二: 打包服务端代码并启动服务第三: 用户访问,服务端读取浏览器端打包好的index.html文件为字符串,将渲染好的组件、样式、数据塞入html字符串,返回给浏览器第四: 浏览器直接渲染接收到的html内容,并且加载打包好的浏览器端js文件,进行事件绑定,初始化状态数据,完成同构React组件的服务端渲染让我们来看一个最简单的React服务端渲染的过程。要进行服务端渲染的话那必然得需要一个根组件,来负责生成HTML结构import React from ‘react’;import ReactDOM from ‘react-dom’;ReactDOM.hydrate(<Container />, document.getElementById(‘root’));当然这里用ReactDOM.render也是可以的,只不过hydrate会尽量复用接收到的服务端返回的内容,来补充事件绑定和浏览器端其他特有的过程引入浏览器端需要渲染的根组件,利用react的 renderToString API进行渲染import { renderToString } from ‘react-dom/server’import Container from ‘../containers’// 产生htmlconst content = renderToString(<Container/>)const html = &lt;html&gt; &lt;body&gt;${content}&lt;/body&gt; &lt;/html&gt;res.send(html)在这里,renderToString也可以替换成renderToNodeStream,区别在于前者是同步地产生HTML,也就是如果生成HTML用了1000毫秒,那么就会在1000毫秒之后才将内容返回给浏览器,显然耗时过长。而后者则是以流的形式,将渲染结果塞给response对象,就是出来多少就返回给浏览器多少,可以相对减少耗时路由的服务端渲染一般场景下,我们的应用不可能只有一个页面,肯定会有路由跳转。我们一般这么用:import { BrowserRouter, Route } from ‘react-router-dom’const App = () => ( <BrowserRouter> {/…Routes/} <BrowserRouter/>)但这是浏览器端渲染时候的用法。在做服务端渲染时,需要使用将BrowserRouter 替换为 StaticRouter区别在于,BrowserRouter 会通过HTML5 提供的 history API来保持页面与URL的同步,而StaticRouter则不会改变URLimport { createServer } from ‘http’import { StaticRouter } from ‘react-router-dom’createServer((req, res) => { const html = renderToString( <StaticRouter location={req.url} context={{}} > <Container /> <StaticRouter/>)})这里,StaticRouter要接收两个属性:location: StaticRouter 会根据这个属性,自动匹配对应的React组件,所以才会实现刷新页面,服务端返回的对应路由的组与浏览器端保持一致context: 一般用来传递一些数据,相当于一个载体,之后讲到样式的服务端渲染的时候会用到Redux同构数据的预获取以及脱水与注水我认为是服务端渲染的难点。这是什么意思呢?也就是说首屏渲染的网页一般要去请求外部数据,我们希望在生成HTML之前,去获取到这个页面需要的所有数据,然后塞到页面中去,这个过程,叫做“脱水”(Dehydrate),生成HTML返回给浏览器。浏览器拿到带着数据的HTML,去请求浏览器端js,接管页面,用这个数据来初始化组件。这个过程叫“注水”(Hydrate)。完成服务端与浏览器端数据的统一。为什么要这么做呢?试想一下,假设没有数据的预获取,直接返回一个没有数据,只有固定内容的HTML结构,会有什么结果呢?第一:由于页面内没有有效信息,不利于SEO。第二:由于返回的页面没有内容,但浏览器端JS接管页面后回去请求数据、渲染数据,页面会闪一下,用户体验不好。我们使用Redux来管理状态,因为有服务端代码和浏览器端代码,那么就分别需要两个store来管理服务端和浏览器端的数据。组件的配置组件要在服务端渲染的时候去请求数据,可以在组件上挂载一个专门发异步请求的方法,这里叫做loadData,接收服务端的store作为参数,然后store.dispatch去扩充服务端的store。class Home extends React.Component { componentDidMount() { this.props.callApi() } render() { return <div>{this.props.state.name}</div> }}Home.loadData = store => { return store.dispatch(callApi())}const mapState = state => stateconst mapDispatch = {callApi}export default connect(mapState, mapDispatch)(Home)路由的改造因为服务端要根据路由判断当前渲染哪个组件,可以在这个时候发送异步请求。所以路由也需要配置一下来支持loadData方法。服务端渲染的时候,路由的渲染可以使用react-router-config这个库,用法如下(重点关注在路由上挂载loadData方法):import { BrowserRouter } from ‘react-router-dom’import { renderRoutes } from ‘react-router-config’import Home from ‘./Home’export const routes = [ { path: ‘/’, component: Home, loadData: Home.loadData, exact: true, }]const Routers = <BrowserRouter> {renderRoutes(routes)}<BrowserRouter/>服务端获取数据到了服务端,需要判断匹配的路由内的所有组件各自都有没有loadData方法,有就去调用,传入服务端的store,去扩充服务端的store。同时还要注意到,一个页面可能是由多个组件组成的,会发各自的请求,也就意味着我们要等所有的请求都发完,再去返回HTML。import express from ’express’import serverRender from ‘./render’import { matchRoutes } from ‘react-router-config’import { routes } from ‘../routes’import serverStore from “../store/serverStore"const app = express()app.get(’*’, (req, res) => { const context = {css: []} const store = serverStore() // 用matchRoutes方法获取匹配到的路由对应的组件数组 const matchedRoutes = matchRoutes(routes, req.path) const promises = [] for (const item of matchedRoutes) { if (item.route.loadData) { const promise = new Promise((resolve, reject) => { item.route.loadData(store).then(resolve).catch(resolve) }) promises.push(promise) } } // 所有请求响应完毕,将被HTML内容发送给浏览器 Promise.all(promises).then(() => { // 将生成html内容的逻辑封装成了一个函数,接收req, store, context res.send(serverRender(req, store, context)) })})细心的同学可能注意到了上边我把每个loadData都包了一个promise。const promise = new Promise((resolve, reject) => { item.route.loadData(store).then(resolve).catch(resolve) console.log(item.route.loadData(store));})promises.push(promise)这是为了容错,一旦有一个请求出错,那么下边Promise.all方法则不会执行,所以包一层promise的目的是即使请求出错,也会resolve,不会影响到Promise.all方法,也就是说只有请求出错的组件会没数据,而其他组件不会受影响。注入数据我们请求已经发出去了,并且在组件的loadData方法中也扩充了服务端的store,那么可以从服务端的数据取出来注入到要返回给浏览器的HTML中了。来看 serverRender 方法const serverRender = (req, store, context) => { // 读取客户端生成的HTML const template = fs.readFileSync(process.cwd() + ‘/public/static/index.html’, ‘utf8’) const content = renderToString( <Provider store={store}> <StaticRouter location={req.path} context={context}> <Container/> </StaticRouter> </Provider> ) // 注入数据 const initialState = &lt;script&gt; window.context = { INITIAL_STATE: ${JSON.stringify(store.getState())} }&lt;/script&gt; return template.replace(’<!–app–>’, content) .replace(’<!–initial-state–>’, initialState)}浏览器端用服务端获取到的数据初始化store经过上边的过程,我们已经可以从window.context中拿到服务端预获取的数据了,此时需要做的事就是用这份数据去初始化浏览器端的store。保证两端数据的统一。import { createStore, applyMiddleware, compose } from ‘redux’import thunk from ‘redux-thunk’import rootReducer from ‘../reducers’const defaultStore = window.context && window.context.INITIAL_STATEconst clientStore = createStore( rootReducer, defaultStore,// 利用服务端的数据初始化浏览器端的store compose( applyMiddleware(thunk), window.devToolsExtension ? window.devToolsExtension() : f=>f ))至此,服务端渲染的数据统一问题就解决了,再来回顾一下整个流程:用户访问路由,服务端根据路由匹配出对应路由内的组件数组循环数组,调用组件上挂载的loadData方法,发送请求,扩充服务端store所有请求完成后,通过store.getState,获取到服务端预获取的数据,注入到window.context中浏览器渲染返回的HTML,加载浏览器端js,从window.context中取数据来初始化浏览器端的store,渲染组件这里还有个点,也就是当我们从路由进入到其他页面的时候,组件内的loadData方法并不会执行,它只会在刷新,服务端渲染路由的时候执行。这时候会没有数据。所以我们还需要在componentDidMount中去发请求,来解决这个问题。因为componentDidMount不会在服务端渲染执行,所以不用担心请求重复发送。样式的服务端渲染以上我们所做的事情只是让网页的内容经过了服务端的渲染,但是样式要在浏览器加载css后才会加上,u偶遇最开始返回的网页内容没有样式,页面依然会闪一下。为了解决这个问题,我们需要让样式也一并在服务端渲染的时候返回。首先,服务端渲染的时候,解析css文件,不能使用style-loader了,要使用isomorphic-style-loader。{ test: /.css$/, use: [ ‘isomorphic-style-loader’, ‘css-loader’, ‘postcss-loader’ ],}我们想,如何在服务端获取到当前路由内的组件样式呢?回想一下,我们在做路由的服务端渲染时,用到了StaticRouter,它会接收一个context对象,这个context对象可以作为一个载体来传递一些信息。我们就用它!思路就是在渲染组件的时候,在组件内接收context对象,获取组件样式,放到context中,服务端拿到样式,插入到返回的HTML中的style标签。来看看组件是如何读取样式的吧:import style from ‘./style/index.css’class Index extends React.Component { componentWillMount() { if (this.props.staticContext) { const css = styles._getCss() this.props.staticContext.css.push(css) } }}在路由内的组件可以在props里接收到staticContext,也就是通过StaticRouter传递过来的context,isomorphic-style-loader 提供了一个 _getCss() 方法,让我们能读取到css样式,然后放到staticContext里。不在路由之内的组件,可以通过父级组件,传递props的方法,或者用react-router的withRouter包裹一下在服务端,经过组件的渲染之后,context中已经有内容了,我们这时候把样式处理一下,返回给浏览器,就可以做到样式的服务端渲染了const serverRender = (req, store) => { const context = {css: []} const template = fs.readFileSync(process.cwd() + ‘/public/static/index.html’, ‘utf8’) const content = renderToString( <Provider store={store}> <StaticRouter location={req.path} context={context}> <Container/> </StaticRouter> </Provider> ) // 经过渲染之后,context.css内已经有了样式 const cssStr = context.css.length ? context.css.join(’\n’) : ’’ const initialState = &lt;script&gt; window.context = { INITIAL_STATE: ${JSON.stringify(store.getState())} }&lt;/script&gt; return template.replace(’<!–app–>’, content) .replace(‘server-render-css’, cssStr) .replace(’<!–initial-state–>’, initialState)}至此,服务端渲染就全部完成了。总结React的服务端渲染,最好的解决方案就是Next.js。如果你的应用没有SEO优化的需求,又或者不太注重首屏渲染的速度,那么尽量就不要用服务端渲染。因为会让项目变得复杂。此外,除了服务端渲染,SEO优化的办法还有很多,比如预渲染(pre-render)。 ...

March 27, 2019 · 2 min · jiezi

React-router BrowerRouter 刷新404 Apache服务器解决办法

1.找到etc/apache2/sites-availables/000-defautl.conf配置文件2.添加以下配置 <VirtualHost :80> … <Directory “/var/www/html/"> Options Indexes FollowSymLinks AllowOverride all Require all granted </Directory></VirtualHost>3.重新apache服务器4 在项目文件根目录下,添加.htaccess文件,加入如下规则RewriteEngine On RewriteBase / RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-l RewriteRule ^.$ / [L,QSA]5.参考链接

March 9, 2019 · 1 min · jiezi

[ 一起学React系列 -- 12 ] React-Router4 (2)

时隔那么久,博主终于从睡梦中醒来开始更新博客啦!为自己的勤劳欢呼…(pia pia pia打脸)!本次我们接着上一篇博客继续聊React-Router4。上篇我们主要了解了React-Router4常用组件以及常用的路由类型,本次我们接着说“嵌套路由”和“验证路由”。嵌套路由顾名思义,嵌套路由其实就是在某个路由的末端再接上一个包含路由的组件,这样就形成了嵌套路由。最直接的说,本片博客的例子代码结构就是嵌套路由。形象化一点,我们可以将项目中的一整套路由想象成一棵树,树根是root路由,向上就是一些分叉的树枝(子路由),分叉的树枝再顺着向上找还会有更多的分叉,这样就是“嵌套”。说的再多没有一个图来得清楚!从根/A到中间一段/A/B,再到末端(叶子节点)/A/B/C。这就是嵌套路由相对合理的解释了。这里我们看下部分有代表性的代码:…<li><NavLink exact activeStyle={{ fontWeight: ‘bold’, color: ‘red’ }} to={${URL}/Fronted}>/Fronted</NavLink></li><li><NavLink activeStyle={{ fontWeight: ‘bold’, color: ‘red’ }} to={${URL}/Fronted/WebPack}>/Fronted/WebPack</NavLink></li>…<Route path={${PATH}/Fronted} component={() => <Technology path={PATH} />} />…可以看出${URL}/Fronted对应的组件是Technology。再看下Technology的代码:class Technology extends Component { render() { const PATH = this.props.path; return ( <Switch> <Route path={${PATH}/Fronted/:name} component={Name} /> <Route path={${PATH}/Fronted} component={Fronted} /> </Switch> ) }}可以看出Technology组件中也包含了一层路由。上篇中博主有说过,不要在非末端路由使用exact,相当于示例图中的/A/B,一旦在这个路由<Route/>中使用了exact,那么匹配路由的时候一旦到了该出路由后就不会寻找到/A/B/C,因为已经被exact “截断” 了。验证路由所谓的验证路由其实就是该路由的外层加了一层验证机制,有授权的用户才能进入,反之都无法进入。验证路由实现起来也很简单,其实就是对某一个用来做验证的参数进行校验,例子中有具体的代码实现。什么?这就结束了?当然不是,与其把验证路由的实现方法说一遍不如将withRouter这个方法普及下,授人以鱼不如授人以渔。withRouter从名字可以看出这个方法其实和Router有关。废话,本篇不就是在说Router吗?好吧!说的也是。但是我们得换个角度去看,它到底和Router有什么样的关系呢?先一起回想一下,如果想获得history,location,match这三个对象?如何去做?认真看例子代码的盆友肯定会注意到,只有在<Route/>中跳转到的组件才能在props中获取到这三个对象。<Route path={${PATH}/Fronted} component={Fronted} />class Fronted extends Component { render() { console.log(this.props); return ( <h1>Fronted</h1> ) }}打印出来的结果但如果不这样做该用哪种方式去获得这三个对象呢?那就轮到withRouter大展身手了。放码过来了const AuthButton = withRouter( ({ history }) => AuthTool.isAuthenticated ? ( <div> Welcome!{" “} <button onClick={() => { AuthTool.logout(() => history.push("/auth/Auth/Login”)); //登出后跳转到登录页面 }} > Logout </button> </div> ) : ( <div> You are not logged in! <button onClick={() => { AuthTool.login(() => history.push("/auth/Auth")); //登出后跳转到实际页面 }} > Login </button> </div> ));withRouter接受一个方法或者任何一个自定义的组件。这样就可以获得我们需要的跟路由有关的对象了。本篇篇幅不是很长,但介绍的方法着实很有实用的啊。emmmmm…《一起学React系列》也随着这篇的结束而告一段落了。在此感谢大家的关注,也很感谢自己能坚持写博文。大家一起加油!!!!最后再献上和本篇博文有关的代码链接和示例页面 ...

March 9, 2019 · 1 min · jiezi

React Router4.0

React Router v4是对React Router的一次彻底重构,采用动态路由,遵循React中一切皆组件的思想,每一个Route(路由)都是一个普通的React组件。BrowserRouter创建的URL形式如下:http://react.com/some/pathHashRouter创建的URL形式如下:http://react.com/#/some/path使用BrowserRouter时,一般还需要对服务器进行配置,让服务器能正确处理所有可能的URL.例如,当浏览器发送 http://react.com/some/path 和 http://react.com/some/path2两…,服务器能够返回正确的HTML页面(也就是单页面应用中唯一的html页面).使用HashRouter则不存在这个问题,因为hash部分的内容会被服务器自动忽略,真正有效的是hash前面的部分,而对于单页面应用来说,这部分内容是固定的。路由的配置1.path(1)当使用BrowserRouter时,path用来描述这个Route匹配的URL的pathname(2)当使用HashRouter时,path用来描述这个Route匹配的URL的hash.2.match(1)params: Route的path可以包含参数,例如:<Route path=’/foo/:id’> 包含一个参数id。params就是用于从匹配的URL中解析出path中的参数,例如:当URL=“http://react.com/foo/1时,params={id:1}。(2)isExact: 是一个布尔值,当URL完全匹配时,值为true;当URL部分匹配时,值为false.例如:当path="/foo”,URL=“http://react.com/foo"时,是完全匹配;当URL=“http://react.com/foo/1时,是部分匹配。(3)path: Route的path属性,构建嵌套路由时会使用到。(4)url: URL的匹配部分。3.Route渲染组件的方式(1)componentcomponent的值是一个组件,当URL和Route匹配时,component属性定义的组件就会被渲染。<Route path=’/foo’ component={FOO}>当URL=“http://react.com/foo"时,Foo组件会被渲染。(2)renderrender的值是一个函数,这个函数返回一个React元素,这个函数返回一个React元素。这种方式可以很方便的为待渲染的组件传递额外的属性。例如:<Route path=’/foo’ render={(props)=>(<Foo {…props} data={extraProps} />)}>Foo组件接收了一个额外的data属性。(3)children children的值也是一个函数,函数返回要渲染的React元素。与之前两种方式不同的是,无论是否匹配成功,children返回的组件都将会被渲染。但是当匹配不成功时,match属性为null。例如:<Route path=’/foo’ children={(props)=>(<div className={props.match?‘active’:’’}> <Foo /></div>)} />如果Route匹配当前URL,待渲染元素的根节点div的class将被设置成active。4.Switch和exact当URL和多个Route匹配时,这些Route都会执行渲染操作。如果只想让第一个匹配的Router渲染,那么可以把这些Route包到一个Switch组件中。如果想让URL和Route完全匹配时,Route才渲染,那么可以使用Route的exact属性。Switch和exact常常联合使用,用于应用首页的导航。例如:<Router><Switch> <Route exact path=’/’ component={Home} /> <Route path=’/posts’ component={Posts} /> <Route path=’/:user’ component={User} /></Switch></Router>如果不使用Switch,当URL的pathname为”/posts"时,<Route path=’/posts’/>和<Route path=’/:user’ />都会被匹配。如果不使用exact,”/” “/posts” “/user1"等几乎所有URL都会匹配第一个Route,又因为Switch的存在,后面的两个Route永远也不会被匹配。使用exact,保证只有当URL的pathname为”/“时,第一个Route才会被匹配。5.嵌套路由嵌套路由是指在Route渲染的组件内部定义新的Route.例如:const Posts = ({match}) => {return( <div> {/这里match.url等于/posts/} <Route path={${match.url}/:id} component={PostDetail} /> <Route exact path={match.url} component={PostList} /> </div>)}当URL的pathname为”/posts/react"时,PostDetail组件会被渲染;当URL的pathname为"/posts"时,PostList组件会被渲染。Route的嵌套使用让应用可以更加灵活的使用路由。6.链接Link是React Router提供的链接组件,一个Link组件定义了当点击该Link时,页面应该如何路由:例如:const Navigation = () => (<header> <nav> <ul> <li><Link to=’/’>Home</Link></li> <li><Link to=’/posts’>Posts</Link></li> </ul> </nav></header>)Link使用to属性声明要导航到URL地址。to可以是string或object类型,当to为object类型时,可以包含pathname、search、hash、state、四个属性,例如:<Link to={{pathname:’/posts’,search:’?sort=name’,hash:’#the-hash",state:{formHome:true}}}/>除了使用Link外,我们还可以使用history对象手动实现导航,history中最常用的方法是push(path,[state])和replace(path,[state]),push会向浏览器历史记录中新增一条记录,replace会用新纪录替换当前纪录,例如:history.push(’/posts’)history.replace(’/posts’)import React from “react”;import { BrowserRouter as Router, Route, Link } from “react-router-dom”;function BasicExample() { return (<Router> <div> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/about">About</Link> </li> <li> <Link to="/topics">Topics</Link> </li> </ul> <hr /> <Route exact path="/" component={Home} /> <Route path="/about" component={About} /> <Route path="/topics" component={Topics} /> </div></Router>);}function Home() { return (<div> <h2>Home</h2></div>);}function About() { return (<div> <h2>About</h2></div>);}function Topics({ match }) { return (<div> <h2>Topics</h2> <ul> <li> <Link to={${match.url}/rendering}>Rendering with React</Link> </li> <li> <Link to={${match.url}/components}>Components</Link> </li> <li> <Link to={${match.url}/props-v-state}>Props v. State</Link> </li> </ul> <Route path={${match.path}/:topicId} component={Topic} /> <Route exact path={match.path} render={() => <h3>Please select a topic.</h3>} /></div>);}function Topic({ match }) { return (<div> <h3>{match.params.topicId}</h3></div>);}export default BasicExample; ...

January 30, 2019 · 1 min · jiezi

使用redux,react在纯函数中触发react-router-dom页面跳转

文章有错误和不合理的地方欢迎小伙伴轻拍看到标题,很多react选手可能就会笑了,这还是问题吗?在函数中触发页面跳转不就的用redux吗!或者redux类似的控件,mbox,dva,rxjs等等,或者自己写个订阅功能的控件,可能就是因为太简单了,网上的解决这个问题的文章才那么少。当我试着用react搭建前端框架,这个问题确实困扰了我好久,当接触到redux就是这redux是正解了。痛点这就是困扰我的问题对fetch()进行封装,并对请求的返回数据做拦截,当捕捉到错误的时候,判断错误的状态码,404时让页面跳转到404页面,当时401时跳转到登录页面,500调整到500页面。 react-router ^4并没有暴露出来history对象,这让非组件内页面跳转变的困难。问题的解决定义storefunction navTo(state = “/”, action) { switch (action.type) { case ‘NAV_TO’: return action.path; default: return state }}let store = createStore(combineReducers({navTo}));export default store;fetch()状态拦截代码import store from “../../redux/store”;fetch(builUrl(url, params), requestInit) .then(data => { return data.json() }).catch(e => { const status = e.name; if (status === 401) { store.dispatch({type: ‘NAV_TO’, path: ‘/login’}); return; } if (status === 403) { store.dispatch({type: ‘NAV_TO’, path: ‘/exception/403’}); return; } if (status <= 504 && status >= 500) { store.dispatch({type: ‘NAV_TO’, path: ‘/exception/500’}); return; } if (status >= 404 && status < 422) { store.dispatch({type: ‘NAV_TO’, path: ‘/exception/404’}); return; } })app.js实现对store的订阅,并跳转页面import React, {Component} from ‘react’;import store from ‘./app/common/redux/store.js’import {withRouter} from “react-router-dom”;@withRouterclass App extends Component { constructor(props) { super(props); store.subscribe(() => { this.props.history.push(store.getState().navTo); }); } render() { return ( <div> {this.props.children} </div> ); }}export default App;当fetch()拦截到错误就可以进行页面调整了如果对redux有疑问,可以看我另一篇文章https://segmentfault.com/a/11… 这就是在函数中通过订阅的方式来实现页面跳转,easy easy !!!小伙伴可以举一反三运用到更多的地方去!!????????????????????如果能帮助到小伙伴的话欢迎点个赞????????????????????????????????????????如果能帮助到小伙伴的话欢迎点个赞???????????????????? ...

January 24, 2019 · 1 min · jiezi

[ 一起学React系列 -- 11 ] React-Router4 (1)

2019年不知不觉已经过去19天了,有没有给自己做个总结?有没有给明年做个计划?当然笔者已经做好了明年的工作、学习计划;同时也包括今年剩下的博文计划,在这里透露下:年前(对,没有写错,是年前)完成该系列博客,目前还剩4篇:分别是两篇React-Router4和两篇immutability-helper。本篇是React-Router4的第一篇,在正式开始之前大家可以先看下实际效果,这是笔者用React-Router4写的例子。React-Router4实际上,笔者接触的第一个React中的路由模块就是React-Router,只不过当时还是v3版本。因为没有太多深入得学习研究,所以在这里不会对v4之前的版本作介绍或者评价(其实并不代表笔者对v4版本有深入的研究学习,汗…)。下面列举些React-Router4中需要知道的一些概念或者emmmm…小知识点。React-Router4中的包React-Router4中的包主要有三个react-router、react-router-dom和react-router-native。据考证(手动斜眼笑),React-Router4已经将前身切分了出来分别整合成了单独的module。其实笔者一开始看到也挺懵逼的,这三个包到底是什么玩意?react-router:“The core of React Router”。简单说就是逻辑代码。react-router-dom:“DOM bindings for React Router”。这个模块不仅仅包含了react-router的模块,还包含了页面渲染功能,也就是可以在页面上显示。react-router-native:“React Native bindings for React Router”。这个也很好理解,就是可以在React-Native中使用。路由根节点React-Router4的理念是一切皆组件。React-router-dom则提供了两个路由根节点:HashRouter和BrowserRouter。HashRouter: 通过hash值来对路由进行控制,而且你会发现一个现象就是url中会有个#,例如localhost:3000/#。对于笔者这种有强迫症的人来说怎么能忍?所以笔者就没再碰这个组件。BrowserRouter: BrowserRouter就相对舒服点。它的原理是使用HTML5 history API (pushState, replaceState, popState)来使你的内容随着url动态改变的,而不会出现莫名其妙的#。React-Router4的理念上面提到:React-Router4的理念是一切皆组件(以下统一称“组件”)。我们v4之前的版本都需要在一个js文件中配置整个项目的路由信息然后在App.js(以create-react-app脚手架创建的React项目为例)引入并使用。而在React-Router4中则将所有的功能以React组件的形式提供出来,意思就是说我们可以像使用普通组件一样使用路由组件并最终在项目中形成一棵路由树。在这里笔者要注重说下,一个项目中尽量只有一棵路由树,除非有特殊需求,否则的话会造成一些奇怪的问题。通俗点说就是不要在一棵树上栽另一棵树。下面用一张图来简单展示下React-Router4的使用:如图是React-Router4使用规则和最简单的使用实践。当然这种写法(我称它叫组件型路由,下同)和之前的配置型路由哪个更好用是青菜萝卜的问题,不需要太多纠结,自己喜欢就好。接下来的介绍都是笔者通过学习官方文档并做了一些实践后的心得。如有不当或者错误,欢迎指正。对于第一次学习该模块的童鞋,笔者建议将代码下载下来一遍看代码一遍看本文,也可以打开笔者的demo,这样会更深刻。常用路由组件介绍下面这段代码是该demo根路由配置的代码(其实不存在什么根路由概念,只是笔者喜欢这么叫它)。从这里就能明显看出来react-router3和4两个版本的差异。react-router3有自己独立的一个配置中心文件,而react-router4则把跳转的规则嵌入到实际代码中,不需要额外维护一个路由配置文件。从这点来说的确方便了不少,也迎合React一切皆组件的理念。<Router> <div className={AppStyle[“Container-body”]}> <nav className={AppStyle[“App-nav-list”]}> <ul> <li><NavLink activeStyle={{ fontWeight: ‘bold’, color: ‘red’ }} to="/basic">基础路由</NavLink></li> <li><NavLink activeStyle={{ fontWeight: ‘bold’, color: ‘red’ }} to="/param">带参路由</NavLink></li> <li><NavLink activeStyle={{ fontWeight: ‘bold’, color: ‘red’ }} to="/nesting">嵌套路由</NavLink></li> <li><NavLink activeStyle={{ fontWeight: ‘bold’, color: ‘red’ }} to="/ambiguous">暧昧匹配</NavLink></li> <li><NavLink activeStyle={{ fontWeight: ‘bold’, color: ‘red’ }} to="/auth">权限路由</NavLink></li> <li><NavLink activeStyle={{ fontWeight: ‘bold’, color: ‘red’ }} to="/404">404</NavLink></li> </ul> </nav> <div className={AppStyle.content}> <Switch> <Redirect exact from="/" to="/basic" /> <Route path="/basic" component={renderBasicRouter} /> <Route path="/param" component={RouterWithParameters} /> <Route path="/ambiguous" component={renderAmbiguousRouter} /> <Route path="/nesting" component={renderNestingRouter} /> <Route path="/auth" component={authenticationRouter} /> <Route component={NoMatch} /> </Switch> </div> </div></Router>从这段代码中需要了解的概念包括:Router、NavLink、Route、Redirect、exact、Switch。Router上面已经介绍过了,这里笔者用的是BrowserRouterNavLinkNavLink可以触发路由的跳转,当然类似的组件还有Link。它们都可以通过指定属性to的值来告诉Router我们要跳转到那个Route,实际上NavLink(Link)和Route本就已经通过to和path两个属性建立起关系了。而NavLink与Link的区别在于各自API的数量,因为NavLink可用的API相对较多,所以笔者更青睐NavLink。具体API有兴趣的可以自行Google,常用的API笔者已经在项目中使用。RouteRoute组件是React Router4中主要的组成单位,可以认为是NavLink或Link的路由入口。在这里笔者要重点Tip一下,上一条说NavLink或Link可以触发路由的跳转,实际上它们的实现流程是这样的:NavLink(Link)改变地址栏的pathname,Router会根据pathname去匹配它子组件中的Route中的path属性,一旦匹配上就会渲染该Route对应的组件。所以NavLink(Link)与Route并没有并存的关系,因为NavLink(Link)只是用来改变pathname,不会直接去调用任何API。所以当我们在地址栏直接手动输入路由,也会发生路由渲染。具体匹配规则请参考path-to-regexp,或者通过这个网站进行测试。RedirectRedirect相当于转发器。Router内部去匹配路由入口的时候也会去匹配Redirect的from属性值。一旦匹配到了Redirect,Redirect就会将这个跳转请求转向自己的to属性值对应的路由。所以这个过程可以这样理解:当我们访问页面路径的时候,比如:http://ip:3001/,就会捕获到'/'这个路由跳转请求,Router开始在Route中匹配随后匹配到了Redirect,Redirect自行发起路由跳转请求'/basic',Router开始像往常一样在Route中匹配直到匹配到了path为"/basic"的Route,随后对Route对应的component进行渲染。exactexact将该Route标示为严格匹配路由。什么叫严格匹配路由?就是pathname必须与Route的path属性值完全一致才算匹配上。例如:pathnamepath匹配结果/home/home可以匹配/home/child/home无法匹配这里Tip下:如果某个Route对应的组件中也有Route,那么千万不要 在这个Route中加 exact,不然你会发现完全匹配不到子路由。切记,因为笔者最近刚踩过这个坑。所以这里笔者建议大家只在叶子Route中使用exact,也就是最后一级Route中使用exact,当然 exact '/' 除外。SwitchSwitch可以将多个Route包裹成一组Route,当进行路由匹配时候,该组中的路由一旦发生匹配那么就不会匹配改组中剩下的路由。也就是说该组中的路由最多只会被匹配到一个。Route的component属性追加一条。Route的component属性对应的属性值通常是一个组件或者一个方法。而如果是方法的话,比如例子中:const renderBasicRouter = ({ match }) =&gt; &lt;Basic url={match.url} path={match.path} /&gt;;那么传入的参数为:所以如果在跳转前有什么特殊逻辑需要处理,比如我们想让不同的页面有不同的前缀,比如例子中的/basic/Home、/param/home等,那么就可以如例子一样处理。但如果不需要特殊处理的话,直接把组件放到component属性下即可。当然router相关的参数也会通过props传给该组件。这里Tip下:1、Route中还有一个render属性,也可以用来渲染组件,但是当我们渲染被Redux处理过的组件时候可能会有问题,因为Redux会在原组件基础上多包裹一层。2、如果当然常用路由组件还不仅仅这些,后续例子会有补充。基础路由基础路由的使用比较简单,前面的介绍其实已经把它基础使用方法已经说了。不过笔者这里有个小tip:match.url 常用于NavLink或者Link中拼接路由路径,比如:&lt;NavLink to={${URL}/Home}&gt;&lt;/NavLink&gt;match.path常用于Route中拼接路由入口路径,比如:&lt;Route path={${PATH}/Home} component={HomePage} /&gt;这里Tip下:当URL中不带有任何参数的时候,match.path和match.url完全一致。如果带有参数的话可能会有点编码上的差异。带参路由带参路由在实际开发中用的比较多,这里只介绍location.pathname中的参数,location.search笔者没有研究过所以就不说了,免得误导大家。我们可以先看下demo例子的部分代码:&lt;nav className={Style["Params-nav-list"]}&gt; &lt;ul&gt; &lt;li&gt;&lt;NavLink exact activeStyle={{ fontWeight: 'bold', color: 'red' }} to={${URL}/name}&gt;/name&lt;/NavLink&gt;&lt;/li&gt; &lt;li&gt;&lt;NavLink exact activeStyle={{ fontWeight: 'bold', color: 'red' }} to={${URL}/name/Mario}&gt;/name/Mario&lt;/NavLink&gt;&lt;/li&gt; &lt;li&gt;&lt;NavLink activeStyle={{ fontWeight: 'bold', color: 'red' }} to={${URL}/check/true}&gt;/check/true&lt;/NavLink&gt;&lt;/li&gt; &lt;/ul&gt;&lt;/nav&gt;&lt;div className={Style.content}&gt; &lt;Switch&gt; &lt;Route exact path={${PATH}/name/:name} component={Name} /&gt; &lt;Route exact path={${PATH}/name} component={Name1} /&gt; &lt;Route exact path={${PATH}/check/:check(true|false)} component={Check} /&gt; &lt;Route component={NoMatch} /&gt; &lt;/Switch&gt;&lt;/div&gt;这里可以看到Route的配置有点不同,用过express的朋友都知道,路由中通过 :xxx 来标示这是一个参数。其实这里也一样。如例子所示我们在 path={${PATH}/name/:name}通过 :name 来标示这是一个参数。然后对应的导航也有 to={${URL}/name/Mario}。所以这个流程就相当于:导航到/name路由并且传 name为Mario的参数。这个参数可以在对应组件的props中拿到(this.props.match.prams.name)。我们还看到 path={${PATH}/check/:check(true|false)},在参数后有个括号并且里面还有管道符,其实这是限定check的参数值必须为true或者false这两个。细心的朋友可能注意到,我在每个Route中加了exact,这样做的好处是可以不用考虑Route的放置次序。举个例子解释下:如果我们想查看某个人的信息,那么跳转路由应该是 /user/4,但如果Route中有:&lt;Switch&gt; &lt;Route exact path={/user} component={User} /&gt; &lt;Route exact path={/user/:id} component={User} /&gt;&lt;/Switch&gt;那么/user/4就会在匹配到/user后停下渲染User组件并且忽略了参数。有人说把Switch去掉不就行了吗?并不是,那么/user/4会同时匹配上这两个路由并且什么都不会渲染,因为它懵逼了,不知道渲染哪个。所以这种情况下需要调整它们的位置&lt;Switch&gt; &lt;Route exact path={/user/:id} component={User} /&gt; &lt;Route exact path={/user} component={User} /&gt;&lt;/Switch&gt;这样就不会出现上述问题了。但是如果在每一个Route中使用exact(前提是这个Route是叶子Route),就不用考虑Route的次序问题了。404路由有时候会由于各种问题出现匹配不到任何Route的情况,这个时候为了更好的用户体验,我们会配置一个404路由,形如:&lt;div className={Style.content}&gt; &lt;Switch&gt; &lt;Route exact path={${PATH}/name/:name} component={Name} /&gt; &lt;Route exact path={${PATH}/name} component={Name1} /&gt; &lt;Route exact path={${PATH}/check/:check(true|false)`} component={Check} /> <Route component={NoMatch} /> </Switch></div>不过笔者发现在根路由中配置一个404后无法全局抓取路由404,不知道是本就如此还是用法有误,所以笔者在每一级路由中都配置了 <Route component={NoMatch} />。写法很简单,照着写就好了,component中传入显示404信息的组件即可。顺便加一下demo源码 ...

January 19, 2019 · 2 min · jiezi

react全家桶从0到1(react-router4、redux、redux-saga)

react全家桶从0到1(最新)本文从零开始,逐步讲解如何用react全家桶搭建一个完整的react项目。文中针对react、webpack、babel、react-route、redux、redux-saga的核心配置会加以讲解,希望通过这个项目,可以系统的了解react技术栈的主要知识,避免搭建一次后面就忘记的情况。代码库:https://github.com/teapot-py/react-demo首先关于主要的npm包版本列一下:react@16.7.0webpack@4.28.4babel@7+react-router@4.3.1redux@4+从webpack开始思考一下webpack到底做了什么事情?其实简单来说,就是从入口文件开始,不断寻找依赖,同时为了解析各种不同的文件加载相应的loader,最后生成我们希望的类型的目标文件。这个过程就像是在一个迷宫里寻宝,我们从入口进入,同时我们也会不断的接收到下一处宝藏的提示信息,我们对信息进行解码,而解码的时候可能需要一些工具,比如说钥匙,而loader就像是这样的钥匙,然后得到我们可以识别的内容。回到我们的项目,首先进行项目的初始化,分别执行如下命令mkdir react-demo // 新建项目文件夹cd react-demo // cd到项目目录下npm init // npm初始化引入webpacknpm i webpack –savetouch webpack.config.js对webpack进行简单配置,更新webpack.config.jsconst path = require(‘path’);module.exports = { entry: ‘./app.js’, // 入口文件 output: { path: path.resolve(__dirname, ‘dist’), // 定义输出目录 filename: ‘my-first-webpack.bundle.js’ // 定义输出文件名称 }};更新package.json文件,在scripts中添加webpack执行命令"scripts": { “dev”: “./node_modules/.bin/webpack –config webpack.config.js”}如果有报错请按提示安装webpack-clinpm i webpack-cli执行webpacknpm run dev如果在项目文件夹下生成了dist文件,说明我们的配置是没有问题的。接入react安装react相关包npm install react react-dom –save更新app.js入口文件import React from ‘reactimport ReactDom from ‘react-dom’;import App from ‘./src/views/App’;ReactDom.render(<App />, document.getElementById(‘root’));创建目录 src/views/App,在App目录下,新建index.js文件作为App组件,index.js文件内容如下:import React from ‘react’;class App extends React.Component { constructor(props) { super(props); } render() { return (<div>App Container</div>); }}export default App;在根目录下创建模板文件index.html<!DOCTYPE html><html><head> <title>index</title> <meta charset=“utf-8”> <meta name=“viewport” content=“width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no”></head><body> <div id=“root”></div></body></html>到了这一步其实关于react的引入就OK了,不过目前还有很多问题没有解决如何解析JS文件的代码?如何将js文件加入模板文件中?Babel解析js文件Babel是一个工具链,主要用于在旧的浏览器或环境中将ECMAScript2015+的代码转换为向后兼容版本的JavaScript代码。安装babel-loader,@babel/core,@babel/preset-env,@babel/preset-reactnpm i babel-loader@8 @babel/core @babel/preset-env @babel/preset-react -Dbabel-loader:使用Babel转换JavaScript依赖关系的Webpack加载器, 简单来讲就是webpack和babel中间层,允许webpack在遇到js文件时用bable来解析@babel/core:即babel-core,将ES6代码转换为ES5。7.0之后,包名升级为@babel/core。@babel相当于一种官方标记,和以前大家随便起名形成区别。@babel/preset-env:即babel-preset-env,根据您要支持的浏览器,决定使用哪些transformations / plugins 和 polyfills,例如为旧浏览器提供现代浏览器的新特性。@babel/preset-react:即 babel-preset-react,针对所有React插件的Babel预设,例如将JSX转换为函数.更新webpack.config.js module: { rules: [ { test: /.js$/, // 匹配.js文件 exclude: /node_modules/, use: { loader: ‘babel-loader’ } } ] }根目录下创建并配置.babelrc文件{ “presets”: ["@babel/preset-env", “@babel/preset-react”]}配置HtmlWebPackPlugin这个插件最主要的作用是将js代码通过<script>标签注入到 HTML 文件中npm i html-webpack-plugin -Dwebpack新增HtmlWebPackPlugin配置至此,我们看一下webpack.config.js文件的完整结构const path = require(‘path’);const HtmlWebPackPlugin = require(‘html-webpack-plugin’);module.exports = { entry: ‘./app.js’, output: { path: path.resolve(__dirname, ‘dist’), filename: ‘my-first-webpack.bundle.js’ }, mode: ‘development’, module: { rules: [ { test: /.js$/, exclude: /node_modules/, use: { loader: ‘babel-loader’ } } ] }, plugins: [ new HtmlWebPackPlugin({ template: ‘./index.html’, filename: path.resolve(__dirname, ‘dist/index.html’) }) ]};执行 npm run start,生成 dist文件夹当前目录结构如下可以看到在dist文件加下生成了index.html文件,我们在浏览器中打开文件即可看到App组件内容。配置 webpack-dev-serverwebpack-dev-server可以极大的提高我们的开发效率,通过监听文件变化,自动更新页面安装 webpack-dev-server 作为 dev 依赖项npm i webpack-dev-server -D更新package.json的启动脚本“dev": “webpack-dev-server –config webpack.config.js –open"webpack.config.js新增devServer配置devServer: { hot: true, // 热替换 contentBase: path.join(__dirname, ‘dist’), // server文件的根目录 compress: true, // 开启gzip port: 8080, // 端口},plugins: [ new webpack.HotModuleReplacementPlugin(), // HMR允许在运行时更新各种模块,而无需进行完全刷新 new HtmlWebPackPlugin({ template: ‘./index.html’, filename: path.resolve(__dirname, ‘dist/index.html’) })]引入reduxredux是用于前端数据管理的包,避免因项目过大前端数据无法管理的问题,同时通过单项数据流管理前端的数据状态。创建多个目录新建src/actions目录,用于创建action函数新建src/reducers目录,用于创建reducers新建src/store目录,用于创建store下面我们来通过redux实现一个计数器的功能安装依赖npm i redux react-redux -D在actions文件夹下创建index.js文件export const increment = () => { return { type: ‘INCREMENT’, };};在reducers文件夹下创建index.js文件const initialState = { number: 0};const incrementReducer = (state = initialState, action) => { switch(action.type) { case ‘INCREMENT’: { state.number += 1 return { …state } break }; default: return state; }};export default incrementReducer;更新store.jsimport { createStore } from ‘redux’;import incrementReducer from ‘./reducers/index’;const store = createStore(incrementReducer);export default store;更新入口文件app.jsimport App from ‘./src/views/App’;import ReactDom from ‘react-dom’;import React from ‘react’;import store from ‘./src/store’;import { Provider } from ‘react-redux’;ReactDom.render( <Provider store={store}> <App /> </Provider>, document.getElementById(‘root’));更新App组件import React from ‘react’;import { connect } from ‘react-redux’;import { increment } from ‘../../actions/index’;class App extends React.Component { constructor(props) { super(props); } onClick() { this.props.dispatch(increment()) } render() { return ( <div> <div>current number: {this.props.number} <button onClick={()=>this.onClick()}>点击+1</button></div> </div> ); }}export default connect( state => ({ number: state.number }))(App);点击旁边的数字会不断地+1引入redux-sagaredux-saga通过监听action来执行有副作用的task,以保持action的简洁性。引入了sagas的机制和generator的特性,让redux-saga非常方便地处理复杂异步问题。redux-saga的原理其实说起来也很简单,通过劫持异步action,在redux-saga中进行异步操作,异步结束后将结果传给另外的action。下面就接着我们计数器的例子,来实现一个异步的+1操作。安装依赖包npm i redux-saga -D新建src/sagas/index.js文件import { delay } from ‘redux-saga’import { put, takeEvery } from ‘redux-saga/effects’export function* incrementAsync() { yield delay(2000) yield put({ type: ‘INCREMENT’ })}export function* watchIncrementAsync() { yield takeEvery(‘INCREMENT_ASYNC’, incrementAsync)}解释下所做的事情,将watchIncrementAsync理解为一个saga,在这个saga中监听了名为INCREMENT_ASYNC的action,当INCREMENT_ASYNC被dispatch时,会调用incrementAsync方法,在该方法中做了异步操作,然后将结果传给名为INCREMENT的action进而更新store。更新store.js在store中加入redux-saga中间件import { createStore, applyMiddleware } from ‘redux’;import incrementReducer from ‘./reducers/index’;import createSagaMiddleware from ‘redux-saga’import { watchIncrementAsync } from ‘./sagas/index’const sagaMiddleware = createSagaMiddleware()const store = createStore(incrementReducer, applyMiddleware(sagaMiddleware));sagaMiddleware.run(watchIncrementAsync)export default store;更新App组件在页面中新增异步提交按钮,观察异步结果import React from ‘react’;import { connect } from ‘react-redux’;import { increment } from ‘../../actions/index’;class App extends React.Component { constructor(props) { super(props); } onClick() { this.props.dispatch(increment()) } onClick2() { this.props.dispatch({ type: ‘INCREMENT_ASYNC’ }) } render() { return ( <div> <div>current number: {this.props.number} <button onClick={()=>this.onClick()}>点击+1</button></div> <div>current number: {this.props.number} <button onClick={()=>this.onClick2()}>点击2秒后+1</button></div> </div> ); }}export default connect( state => ({ number: state.number }))(App);观察结果我们会发现如下报错:这是因为在redux-saga中用到了Generator函数,以我们目前的babel配置来说并不支持解析generator,需要安装@babel/plugin-transform-runtimenpm install –save-dev @babel/plugin-transform-runtime这里关于babel-polyfill、和transfor-runtime做进一步解释babel-polyfillBabel默认只转换新的JavaScript语法,而不转换新的API。例如,Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转译。如果想使用这些新的对象和方法,必须使用 babel-polyfill,为当前环境提供一个垫片。babel-runtimeBabel转译后的代码要实现源代码同样的功能需要借助一些帮助函数,而这些帮助函数可能会重复出现在一些模块里,导致编译后的代码体积变大。Babel 为了解决这个问题,提供了单独的包babel-runtime供编译模块复用工具函数。在没有使用babel-runtime之前,库和工具包一般不会直接引入 polyfill。否则像Promise这样的全局对象会污染全局命名空间,这就要求库的使用者自己提供 polyfill。这些 polyfill一般在库和工具的使用说明中会提到,比如很多库都会有要求提供 es5的polyfill。在使用babel-runtime后,库和工具只要在 package.json中增加依赖babel-runtime,交给babel-runtime去引入 polyfill 就行了;详细解释可以参考babel presets 和 plugins的区别Babel插件一般尽可能拆成小的力度,开发者可以按需引进。比如对ES6转ES5的功能,Babel官方拆成了20+个插件。这样的好处显而易见,既提高了性能,也提高了扩展性。比如开发者想要体验ES6的箭头函数特性,那他只需要引入transform-es2015-arrow-functions插件就可以,而不是加载ES6全家桶。但很多时候,逐个插件引入的效率比较低下。比如在项目开发中,开发者想要将所有ES6的代码转成ES5,插件逐个引入的方式令人抓狂,不单费力,而且容易出错。这个时候,可以采用Babel Preset。可以简单的把Babel Preset视为Babel Plugin的集合。比如babel-preset-es2015就包含了所有跟ES6转换有关的插件。更新.babelrc文件配置,支持genrator{ “presets”: ["@babel/preset-env”, “@babel/preset-react”], “plugins”: [ [ “@babel/plugin-transform-runtime”, { “corejs”: false, “helpers”: true, “regenerator”: true, “useESModules”: false } ] ]}点击按钮会在2秒后执行+1操作。引入react-router在web应用开发中,路由系统是不可或缺的一部分。在浏览器当前的URL发生变化时,路由系统会做出一些响应,用来保证用户界面与URL的同步。随着单页应用时代的到来,为之服务的前端路由系统也相继出现了。而react-route则是与react相匹配的前端路由。引入react-router-domnpm install –save react-router-dom -D更新app.js入口文件增加路由匹配规则import App from ‘./src/views/App’;import ReactDom from ‘react-dom’;import React from ‘react’;import store from ‘./src/store’;import { Provider } from ‘react-redux’;import { BrowserRouter as Router, Route, Switch } from “react-router-dom”;const About = () => <h2>页面一</h2>;const Users = () => <h2>页面二</h2>;ReactDom.render( <Provider store={store}> <Router> <Switch> <Route path="/" exact component={App} /> <Route path="/about/" component={About} /> <Route path="/users/" component={Users} /> </Switch> </Router> </Provider>, document.getElementById(‘root’));更新App组件,展示路由效果import React from ‘react’;import { connect } from ‘react-redux’;import { increment } from ‘../../actions/index’;import { Link } from “react-router-dom”;class App extends React.Component { constructor(props) { super(props); } onClick() { this.props.dispatch(increment()) } onClick2() { this.props.dispatch({ type: ‘INCREMENT_ASYNC’ }) } render() { return ( <div> <div>react-router 测试</div> <nav> <ul> <li> <Link to="/about/">页面一</Link> </li> <li> <Link to="/users/">页面二</Link> </li> </ul> </nav> <br/> <div>redux & redux-saga测试</div> <div>current number: {this.props.number} <button onClick={()=>this.onClick()}>点击+1</button></div> <div>current number: {this.props.number} <button onClick={()=>this.onClick2()}>点击2秒后+1</button></div> </div> ); }}export default connect( state => ({ number: state.number }))(App);点击列表可以跳转相关路由总结至此,我们已经一步步的,完成了一个简单但是功能齐全的react项目的搭建,下面回顾一下我们做的工作引入webpack引入react引入babel解析react接入webpack-dev-server提高前端开发效率引入redux实现一个increment功能引入redux-saga实现异步处理引入react-router实现前端路由麻雀虽小,五脏俱全,希望通过最简单的代码快速的理解react工具链。其实这个小项目中还是很多不完善的地方,比如说样式的解析、Eslint检查、生产环境配置,虽然这几项是一个完整项目不可缺少的部分,但是就demo项目来说,对我们理解react工具链可能会有些干扰,所以就不在项目中加了。后面我会新建一个分支,把这些完整的功能都加上,同时也会对当前的目录结构进行优化。代码库:https://github.com/teapot-py/react-demo ...

January 18, 2019 · 4 min · jiezi

React+React-Route+Antd+Recharts+Excel(获取Excel数据,绘制Charts)

转眼间2018年就过去了,来到的2019新的一年,在这里,祝大家新年快乐。知乎个人博客GithubDemoRepo开发缘由:因为一个很重要的朋友需要绘制一些Charts,但是嫌弃手绘太慢,因此这次放假写了这个小东西当前进度:简单的Demo Charts展示,包括AreaChart, BarChart, ComposedChart, LineChart, PieChart测试文件:src/common/files/info.xlsx附上RechartsReact-Route先上两张照片吧1、版本2、创建项目因为公司使用的 react+antd+ts, 虽然antd前两天搞了个圣诞惊吓,但是毋庸置疑,这个组件库做的确实很好啊,我不怕被喷,辩证一分为二,不能因为别人犯一点的错误,就否认人家吧,废话不多说,还是讲本文的主题吧首先先安装create-react-appnpm i -g create-react-appcreate-react-app Charts –scripts-version=react-scripts-ts-antd然后安装react-route-dom和rechartsyarn add react-route-domornpm i react-route-dom –savenpm i recharts –save因为TS检查较为严格,所以,我对TS有一些我自己需要rules的配置tslint.js{ “extends”: [“tslint:recommended”, “tslint-react”, “tslint-config-prettier”], “linterOptions”: { “exclude”: [ “config//*.js”, “node_modules//.ts”, “coverage/lcov-report/.js” ] }, “rules”: { “no-string-throw”: true, “no-unused-expression”: false, “no-unused-variable”: false, “no-use-before-declare”: false, “no-duplicate-variable”: false, “curly”: true, “class-name”: true, “triple-equals”: [true, “allow-null-check”], “comment-format”: [false, “check-space”], “eofline”: true, “forin”: false, “indent”: [true, “spaces”, 2], “label-position”: true, “max-line-length”: [true, 150], “member-access”: false, “no-arg”: true, “no-bitwise”: false, “no-console”: [true, “debug”, “info”, “time”, “timeEnd”, “trace” ], “no-construct”: true, “no-debugger”: true, “no-empty”: false, “no-eval”: true, “no-inferrable-types”: true, “no-shadowed-variable”: false, “no-string-literal”: false, “no-switch-case-fall-through”: false, “no-trailing-whitespace”: true, “no-var-keyword”: false, “object-literal-sort-keys”: false, “one-line”: [true, “check-open-brace”, “check-catch”, “check-else” ], “radix”: false, “typedef-whitespace”: [true, { “call-signature”: “nospace”, “index-signature”: “nospace”, “parameter”: “nospace”, “property-declaration”: “nospace”, “variable-declaration”: “nospace” }], “variable-name”: [true, “ban-keywords”], “whitespace”: [true, “check-branch”, “check-decl”, “check-type”, “check-preblock” ], “ordered-imports”: false, “jsx-no-lambda”: false, “interface-name”: [true, “never-prefix”], “prefer-const”: false }}TS初试React-Route 4.x提供给我们使用的都是以组件形式存在的。我们使用的时候就像我们以前使用组件那样使用就行了,详见React-Route官方文档。菜单栏,我觉得日后可能还会增加其他的Charts,所以我将菜单通过配置文件来控制,增加复用性。SideMenu.tsximport React, { Component } from ‘react’;import { Link } from ‘react-router-dom’;import classnames from ‘classnames’;import moment from ‘moment’;import ‘./index.scss’;import { menus } from ‘./menus’;import { Layout, Menu, Icon } from ‘antd’;import Timer from ‘src/components/Timer/Timer’;const { Header, Footer, Sider, Content } = Layout;const { SubMenu } = Menu;interface SideMenuProps { children?: any;}export default class SideMenu extends Component<SideMenuProps, any> { public state = { collapsed: false, selectedKeys: [menus[0].key], }; public toggle = () => { this.setState({ collapsed: !this.state.collapsed }); }; render() { const { collapsed, selectedKeys, } = this.state return ( <Layout className=“side-menu”> <Sider trigger={null} collapsible={true} collapsed={collapsed}> <div className=“logo”> <img src={require(‘src/common/images/logo.png’)} /> <a href=“https://github.com/Rain120/charts" target="_blank”> <span className={classnames(“title”)}><Icon type=“github” /></span> </a> <a href=“https://www.zhihu.com/people/yan-yang-nian-hua-120/activities" target="_blank”> <span className={classnames(“title”)}><Icon type=“zhihu” /></span> </a> </div> <Menu theme=“dark” mode=“inline” defaultSelectedKeys={selectedKeys}> { menus && menus.map(menu => ( menu.children ? ( <SubMenu key={menu.key} title={<span><Icon type={menu.icon} /><span>{menu.text}</span></span>}> { menu.children.map(item => ( <Menu.Item key={item.key}> <Link to={item.path}>{item.text}</Link> </Menu.Item> )) } </SubMenu> ) : ( <Menu.Item key={menu.key}> <Link to={menu.path} style={{ overflow: ‘hidden’ }}><Icon type={menu.icon} />{menu.text}</Link> </Menu.Item> ) )) } </Menu> </Sider> <Layout className=“r-content”> <Header> <Icon className=“trigger” type={collapsed ? ‘menu-unfold’ : ‘menu-fold’} onClick={this.toggle} /> <Timer timerStyle=“timer” /> </Header> <Content style={{ margin: ‘1rem’, padding: ‘1rem’, background: ‘#fff’, minHeight: ‘25rem’, }}> {this.props.children} </Content> <Footer style={{ textAlign: ‘center’ }}> ©{moment().format(‘YYYY’)} Created by Rainy </Footer> </Layout> </Layout> ); }}Menu.ts/* * @Author: Rainy * @Github: https://github.com/Rain120 * @Date: 2018-12-30 15:43:12 * @LastEditTime: 2018-12-31 13:28:04 */export const menus = [ { key: ‘menu-0’, icon: ‘bar-chart’, text: ‘Charts Demo Show’, path: ‘/’, }, { key: ‘menu-1’, icon: ‘dashboard’, text: ‘ReCharts’, children: [ { key: ‘1’, text: ‘Charts Drawer’, path: ‘/charts/charts-drawer’ }, ] }] as any;获取Excel的数据是通过使用大佬的xlsx插件来实现的,详见XLSX官网。npm i xlsx -S因为这次项目没有后端,所以其实我们对Excel文件的解析是在upload之前完成的public beforeUpload = (file: any, fileList: any) => { var rABS = true; const f = fileList[0]; var reader = new FileReader(); reader.onload = (e: any) => { let data: any = e.target.result; if (!rABS) { data = new Uint8Array(data); } var workbook = XLSX.read(data, { type: rABS ? ‘binary’ : ‘array’ }); // more sheet workbook.SheetNames.map(item => { var worksheet = workbook.Sheets[item]; var jsonArr = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); this.handleImpotedJson(jsonArr); }) }; if (rABS) { reader.readAsBinaryString(f); } else { reader.readAsArrayBuffer(f); } return false;}upload configconst props = { accept: ‘application/vnd.openxmlformats-officedocument.spreadsheetml.sheet’, name: ‘file’, headers: { authorization: ‘authorization-text’, }, multiple: false, action: ‘’, beforeUpload: (file, fileList) => this.beforeUpload(file, fileList), onChange(info) { const status = info.file.status; if (status !== ‘uploading’) { console.log(info.file, info.fileList); } if (status === ‘done’) { message.success(${info.file.name} file uploaded successfully.); } else if (status === ’error’) { message.error(${info.file.name} file upload failed.); } }, };Charts组件因为使用的Charts比较多,所以使用recharts提供的组件ResponsiveContainer为了使得这些Charts不够缩放的影响。但是当前做的这些Charts大部分都是相同的结构,所以相同的部分应该抽离出来。因为其他的Charts都差不多,这里我只说一下LineChartsimport React, { Component } from ‘react’;import WrapperCharts from ‘./WrapperCharts’;import ‘./index.scss’;import { Line, Legend, Tooltip, XAxis, YAxis, CartesianGrid, LineChart, Label,} from “recharts”;export const COLOR_LISTS = [’#8884d8’, ‘#cf6868’, ‘#3fb549’, ‘#a6d41f’, ‘#8ad4d8’, ‘#cfdd68’, ‘#354449’, ‘#a75d1f’];interface LineChartsProps { data?: any; names?: any;}export default class LineCharts extends Component<LineChartsProps, any> { render() { const { data, names } = this.props; return ( <WrapperCharts class_name=“line-charts”> <LineChart data={data}> <CartesianGrid /> { names && <XAxis dataKey={names[0].dataKey} name={names[0].name} /> } <YAxis /> <Tooltip /> <Legend /> { names && names.slice(1).map((item, index) => ( <Line type=“monotone” dataKey={item.dataKey} key={index} name={item.name} stroke={COLOR_LISTS[index]} /> )) } <Label /> </LineChart> </WrapperCharts> ) }}github page deploynpm i -g gh-pagespackage.json配置"predeploy": “yarn run build”,“deploy”: “gh-pages -d build"部署yarn run deploy引入图片以上就是这两天做的小东西,写的和讲的都很潦草,请看管轻喷。 ...

January 1, 2019 · 4 min · jiezi

react-router-dom多路由共用一个组件时,切换页面地址,页面不刷新的问题

当多个路由使用同一个组件的时候,切换路由的时候,页面组件不重新构建,页面也不刷新当切换路由的时候,只是重新render,并不重新构建,如果需要路由切换的时候,组件重新构建,重新完成一次生命周期,则需要给组件加上key路由不需要修改,我们只需要修改组件,给组件加上key,让router的path属性(params)指向组件的key,就可以实现,组件的重构export default (props)=><User {…props} key={props.location.pathname} />这里的props.location.pathname就是router的path属性的值,这样就实现了,router的path属性指向组件的key这样切换路由的时候,即可完成组件的刷新(重构)参考地址 当多个使用同一个组件的时候,切换页面地址,页面不刷新的问题

December 18, 2018 · 1 min · jiezi

React+TypeScript入门-----BrowserRouter

准备工作:安装react-router-domnpm i react-router-dom -S配置webpack,划重点,如果直接在浏览器地址里输入路径,这个是必须要配置的devServer:{ historyApiFallback:true }先写两个组件备用,非常简单的两个组件class R1 extends React.Component{ render(){ return <div>1</div> }}class R2 extends React.Component{ render(){ return <div>2</div> }}引入BrowserRouter和Route,这两个目前就够用了import { Route, BrowserRouter } from ‘react-router-dom’;创建路由并渲染class Rts extends React.Component{ render(){ return <div className=“test”> <BrowserRouter> <div> <Route exact={true} path="/" component={R1}></Route> <Route exact={true} path="/r2" component={R2}></Route> </div> </BrowserRouter> </div> }}const render = () => { ReactDOM.render( <Rts></Rts> , document.querySelector(’#app’) )}render();打开浏览器默认就是1,然后在浏览器的地址输入 yourServer/r2,就可以看到页面上显示2了使用Link:首先需要引入Link,从react-router-dom多引入一个即可import { Route, BrowserRouter, Link } from ‘react-router-dom’;使用Link创建一个组件class RLink extends React.Component{ render(){ return <ul> <li><Link to="/">显示1</Link></li> <li><Link to="/r2">显示2</Link></li> </ul> }}修改Rts组件,注意标签嵌套层级class Rts extends React.Component{ render(){ return <div className=“test”> <BrowserRouter> <div> <RLink></RLink> <Route exact={true} path="/" component={R1}></Route> <Route exact={true} path="/r2" component={R2}></Route> </div> </BrowserRouter> </div> }}这样就好了,实际效果如图 ...

December 17, 2018 · 1 min · jiezi

从无到有-在create-react-app基础上接入react-router、redux-saga

搭建项目框架新建项目执行如下代码,用create-react-app来建立项目的基础框架,然后安装需要用到的依赖。$ npx create-react-app my-test-project$ cd my-test-project$ yarn add react-router-dom react-redux prop-types redux redux-saga$ yarn start完成后,应用启动在localhost的3000端口。接入react-router-domreact-router-dom其实就是react-router 4.0,与之前的3.0有什么区别呢?react-router被一分为三。react-router、react-router-dom和react-router-native。react-router实现了路由的核心的路由组件和函数。而react-router-dom和react-router-native则是基于react-router,提供了特定的环境的组件。react-router-dom依赖react-router,安装的时候,不用再显示的安装react-router, 如果你有机会去看react-router-dom的源码,就会发现里面有些组件都是从react-router中引入的。新建layout在/src下新建layout目录。为什么要新建layout目录,因为有可能我们会用到多个layout,layout是一个什么样的概念?例如这个应用需要提供一部分功能在微信使用。那么进入所有微信的相关界面下都要进行鉴权。没有鉴权信息就不允许访问,但是这个服务仍然有所有人都可以访问的路由。使用layout可以很好的帮我们解决这个问题。将所有的需要鉴权的页面放在例如WechatContainer下,只有在有微信相关鉴权的信息存在,才允许访问接下来的界面,否则,容器内甚至可以直接不渲染接下来的界面。在/src/layout下新建两个文件,分别是AppLayout.js、WechatLayout.js。AppLayout.js的代码如下。在这个layout中,首页就是单纯的一个路由,导向至首页。而接下来的/wechat则是把路由导向至了一个微信端专用的layout。import React, { Component } from ‘react’;import Home from ‘../routes/home’;import WechatLayout from ‘./WechatLayout’;import { Route, Switch } from ‘react-router-dom’;/** * 项目入口布局 * 在此处根据一级路由的不同进入不同的container * 每个container有自己不同的作用 * * 在react-router V4中,将原先统一在一处的路由分散到各个模块中,分散到各个模块当中 * 例如: WechatLayout的路由为/wechat 表示到该layout下的默认路径 /class AppLayout extends Component { constructor(props) { super(props); this.state = {}; } render() { return ( <div className=‘App’> <main> <Switch> <Route path=’/’ exact component={Home} /> <Route path=’/wechat’ component={WechatLayout} /> </Switch> </main> </div> ); }}export default AppLayout;WechatLayout.js的代码如下。在这个layout中,我们就可以对访问该路由的用户进行鉴权。如果没有权限,我们可以直接限制用户的访问,甚至直接不渲染render中的数据。例如,我们可以在componentWillMount中或者在render中,根据当前的state数据,对当前用户进行鉴权。如果没有权限,我们就可以将当前页面重定向到没有权限的提示界面。import React, { Component } from ‘react’;import Home from ‘../routes/wechat/home’;import { Route, Switch } from ‘react-router-dom’;import { connect } from ‘react-redux’;class WechatLayout extends Component { constructor(props) { super(props); this.state = {}; } componentWillMount() { } render() { const className = ‘Wechat-Layout’; return ( <div className={${className}}> <header> Our Manage Layout </header> <main> <Switch> <Route path={${this.props.match.path}/home} component={Home} /> </Switch> </main> </div> ); }}const mapStateToProps = state => ({ reducer: state.wechatLayout});export default connect(mapStateToProps)(WechatLayout);新建routes新建/src/routes/home/index.js,代码如下。import React, { Component } from ‘react’;import {Link} from ‘react-router-dom’;class Home extends Component { constructor(props) { super(props); this.state = {}; } render() { const className = ‘Home’; return ( <div className={${className}}> <h1>This is Home</h1> <div><Link to={’/wechat/home’}>Manage Home</Link></div> </div> ); }}export default Home;新建/src/routes/wechat/home/index.js, 代码如下。在代码中可以看到,触发reducer很简单,只需要调用dispatch方法即可。dispatch中的payload就是该请求所带的参数,该参数会传到saga中间层,去调用真正的后端请求。并在请求返回成功之后,调用put方法更新state。import React, { Component } from ‘react’;import {connect} from “react-redux”;class Home extends Component { constructor(props) { super(props); this.state = {}; } componentWillMount() { this.props.dispatch({ type: ‘WATCH_GET_PROJECT’, payload: { projectName: ’tap4fun’ } }); } render() { const className = ‘Wechat-Home’; return ( <div className={${className}}> <h1>Home</h1> <h2>The project name is : { this.props.reducer.projectName }</h2> </div> ); }}const mapStateToProps = state => ({ reducer: state.wechat});export default connect(mapStateToProps)(Home)新建container在/src下新建container,在container中新建文件AppContainer.js。我们整个react应用都装在这个容器里面。AppContainer.js的代码如下。而其中的Provider组件,将包裹我们应用的容器AppLayout包在其中,使得下面的所有子组件都可以拿到state。Provider接受store参数作为props,然后通过context往下传递。import React, { Component } from ‘react’;import PropTypes from ‘prop-types’;import { Provider } from ‘react-redux’;import { BrowserRouter as Router } from ‘react-router-dom’;import AppLayout from ‘../layout/AppLayout’;class AppContainer extends Component { constructor(props) { super(props); this.state = {}; } static propTypes = { store: PropTypes.object.isRequired }; render() { const { store } = this.props; return ( <Provider store={store}> <Router> <AppLayout /> </Router> </Provider> ); }}export default AppContainer;修改项目入口文件更新/src/index.js,代码如下。在此处会将create出来的store容器当作属性传入到Appcontainer中,作为我们应用的状态容器。import React from ‘react’;import ReactDOM from ‘react-dom’;import ‘./index.css’;import * as serviceWorker from ‘./serviceWorker’;import AppContainer from ‘./container/AppContainer’;import createStore from ‘./store/createStore’;const store = createStore();ReactDOM.render(<AppContainer store={store} />, document.getElementById(‘root’));// If you want your app to work offline and load faster, you can change// unregister() to register() below. Note this comes with some pitfalls.// Learn more about service workers: http://bit.ly/CRA-PWAserviceWorker.unregister();新建store新建/src/store/craeteStore.js,代码如下。通过以下的方式,我们可以给redux添加很多中间件,甚至是自己写的中间件。比如,我们可以自己实现一个日志中间件,然后添加到中间件数组middleWares中,在创建redux的store的时候,应用我们自己写的中间件。import { applyMiddleware, compose, createStore } from ‘redux’;import createSagaMiddleware from ‘redux-saga’;import rootReducer from ‘../reducers’;import rootSaga from ‘../saga’;export default function configureStore(preloadedState) { // 创建saga中间件 const sagaMiddleware = createSagaMiddleware(); const middleWares = [sagaMiddleware]; const middlewareEnhancer = applyMiddleware(…middleWares); const enhancers = [middlewareEnhancer]; const composedEnhancers = compose(…enhancers); // 创建存储容器 const store = createStore(rootReducer, preloadedState, composedEnhancers); sagaMiddleware.run(rootSaga); return store;}在这引入了redux-saga。我之前在使用redux的时候,几乎在每个模块都要写相应的action和reducer,然后在相应的模块文件中引入action的函数,然后在使用mapDispatchToProps将该函数注入到props中,在相应的函数中调用。并且,一个action不能复用,即使触发的是相同的reducer。这样就会出现很多重复性的代码,新增一个模块的工作也相对繁琐了很多。但是使用了redux-saga之后,只需要在reducer中定义好相应类型的操作和saga就可以了。不需要定义action的函数,不需要在文件中引入action中函数,甚至连mapDispatchToProps都不需要,直接使用this.props.dispatch({ ’type’: ‘WATCH_GET_PROJECT’ })就可以调用。而且,action可以复用。新建saga新建/src/saga/index.js,代码如下。import { put, takeEvery } from ‘redux-saga/effects’;import { delay } from ‘redux-saga’;export function fetchProject() { yield delay(1000); yield put({ type: ‘GET_PROJECT’ })}export default function * rootSaga() { yield takeEvery(‘WATCH_GET_PROJECT’, fetchProject);}新建reducer新建/src/reducers/wechat.js,代码如下。const initialState = { projectName: null};export default function counter(state = initialState, action) { let newState = state; switch (action.type) { case ‘GET_PROJECT’: newState.projectName = action.payload.projectName; break; default: break; } return {…newState}}新建/src/reducers/index.js,代码如下。import { combineReducers } from ‘redux’;import Wechat from ‘./wechat’;export default combineReducers({ wechat: Wechat});在这里我们使用了combineReducers。在之前的基于redux的应用程序中,常见的state结构就是一个简单的JavaScript对象。重新启动应用到此处,重新启动应用,就可以在http://localhost:3000/wechat/home下看到从reducer中取出的数据。在页面中,我们就可以通过代码this.props.dispatch的方式,来触发action。参考https://github.com/mrdulin/bl…https://cn.redux.js.org/docs/…项目源代码Github仓库 ...

December 17, 2018 · 3 min · jiezi

react-router 路由切换动画

路由切换动画因为项目的需求,需要在路由切换的时候,加入一些比较 zb 的视觉效果,所以研究了一下。把这些学习的过程记录下来,以便以后回顾。同时也希望这些内容能够帮助一些跟我一样的菜鸟,让他们少走些坑。可能我对代码的表述不是很到位,希望大家不要介意。机智的你们一定可以看明白。参考内容:react 路由动画react-router Switch 组件react 动画插件1.插件依赖使用的插件是react-transition-group。先简单介绍一下动画插件的使用方式。CSSTransition这个组件有两个比较主要的属性:key和in。in:Boolean属性其实可以理解为是否显示当前内容节点。true则显示,false则不显示。key:String这个属性是配合TransitionGroup组件来使用的。在一般的列表组件中(列如 todolist),可以通过key来判断列表中的子节点需要被插入还是移除,然后触发动画。2. Switch 组件这个组件有一个很重要的属性:location。同时这个属性也是路由切换动画的关键所在。Switch组件的子组件(一般是 Route 和 Redirect)会根据当前浏览器的location作为匹配依据来进行路由匹配。但是如果Switch组件定义了location属性,其中的子组件就会以定义的location作为匹配依据。3.代码部分import React, { Component } from ‘react’import { TransitionGroup, CSSTransition } from ‘react-transition-group’import { Switch, Route, withRouter } from ‘react-router-dom’@withRouterclass Routes extends Component { render() { const location = this.props.location return ( <TransitionGroup> <CSSTransition key={location.key} timeout={1000} classNames=“fade”> <Switch location={location}> <Route path="/route-1" component={Route1} /> <Route path="/route-2" component={Route2} /> </Switch> </CSSTransition> </TransitionGroup> ) }}export default Routes4.原理分析先确定需求:当切换路由的时候,旧的路由内容在一定时间内过渡消失,新的路由内容过渡显示。在这个需要里面有两个重要的部分:过渡期间,会同时存在两个节点:新节点和旧节点旧节点应该显示旧的路由内容,新的节点应该显示新的路由内容4.1 同时存在两节点刚刚提到的CSSTransition的key属性可以决定该节点是否需要显示。而Router中的location属性会在路由发生变化的时候,进行更新,而location里面的key则可以作为CSSTransition的key。关于 history 对象,可以理解为一个数组,当页面的 location 发生变化时,或者刷新页面,history 就会push一个新的历史信息。在这个历史信息里面,有一个key属性,用来区分不同的历史记录(跳转新页面或者是刷新页面)当路由切换的时候,location对象就会改变,新的key会使得页面重新渲染时出现两个CSSTransition(新旧节点)。4.2 新旧节点对应新旧路由内容如果只是配置key属性,会发现旧的节点会去匹配新的路由内容。这是因为Route组件默认根据当前location进行匹配。为了让旧节点中的Route根据旧的location进行匹配,就需要设置Switch的location属性。 ...

December 2, 2018 · 1 min · jiezi