Better

Ethan的博客,欢迎访问交流

React 进阶

React深入和掌握React全家桶在项目上的实践!

react

render函数

返回值就是JSX语法,会把JSX转成JS执行,实际执行的React.createElement

组件只能返回一个根标签,很多时候return需要添加(),是因为针对结构复杂的组件时,为了代码结构清晰,需要显示层级结构,此时将HTML使用括号包裹,return语句紧跟括号,如果return后为空,则会报错。基本原则:多行使用括号。

生命周期

  • 初始化周期
  • 重新渲染生命周期
  • 组件卸载生命周期

react 16

React16是第一个核心代码重写的版本,整体API变化不大。

主要变更了错误处理、生命周期、打包,对开发影响不是特别大

  • 新的核心算法Fiber
  • Render可以数组,字符串
  • 错误处理机制:componentDidCatch
  • Portals组件
  • 更好更快的服务端渲染
  • 体积更小,MIT协议

属性验证库

属性验证库 PropTypes

  • 15以及15以前是内置在react中的,16被抽离了出来
  • npm install prop-types --save
  • 具体类型有
    • PropTypes.array
    • PropTypes.bool
    • PropTypes.func
    • PropTypes.number
    • PropTypes.object
    • PropTypes.string
    • PropTypes.symbol
  • 追加isRequired声明他是必传的

setState

拥抱数据不可变性,this.setState 修改 state,记得,返回新的 state 而不是修改值

chrome扩展

react官方推荐插件:React Developer Tools

Antd-mobild的使用

  • 蚂蚁金服 UI 组件库,专门针对 React
  • npm install antd-mobile@next --save
  • 兼容 Web 和 ReactNative
  • 按需加载
    • 需要 babel-plugin-import 的支持
    • 在 package.json 中babel中添加
      "plugins":[
        ["import",{"libraryName":"antd-mobile","style":"css"}]
      ]
      
  • 常用组件
    • Layout 布局组件
    • 表单组件,数据展示组件,选择器等
    • 操作组件

redux

通过前面的学习,我们已经知道React是一个专注于 view 层的库,对于组件状态的处理,在大型项目中,Redux 是一个不错的选择,Redux 是官方状态管理库。

Redux核心概念

  • store
  • state
  • action
  • reducer
  • dispatch

Redux是什么

  • 专注与状态管理的库,和 react 解耦,在 angular 和 vue 中同样可以使用
  • 单一状态,单项数据流

大概理解:

  • 有一个保险箱(store),所有人的状态在那里都有记录(state)
  • 需要改变的时候,需要告诉专员(dispatch)要干什么(action)
  • 处理变化的人(reducer)拿到state和action,生成新的state

正确的使用方式

  • 首先通过reducer新建store,随时通过store.getState获取状态
  • 需要状态变更,store.dispatch(action)来修改状态
  • reducer函数接受state和action,返回新的state,可以用store.subscribe监听每次修改

Redux如何和React一起使用

  • 手动连接(比较痛苦)
    • 把store.dispatch方法传递给组件,内部可以调用修改状态
    • subscribe订阅render函数,每次修改都会重新渲染
    • redux相关内容,移到单独文件index.redux.js单独管理
  • 处理异步、调试工具、更优雅的和react结合
    • redux处理异步,需要redux-thunk插件
      • npm install redux-thunk --save
      • 使用applyMiddleware开启thunk中间件
      • action可以返回函数,使用dispatch提交action
    • 调试工具
      • chrome扩展Redux DevTools
      • 新建store的时候判断window.devToolsExtension
      • 使用compose结合thunk和window.devToolsExtension
      • 调试窗redux选项卡,实时看到state
    • npm install redux-devtools-extension 并且开启
    • 使用react-redux优雅的链接react和redux
      • npm install react-redux --save
      • 忘记subscribe,记住reducer、action、dispatch即可,也不需要从属性开始传递
      • react-redux提供provider和connect两个接口链接

react-redux具体使用

  • provider组件在应用最外层,传入store即可,只用一次
  • Connect负责从外部获取组件需要的参数
  • Connect可以用装饰器的方式来写
    • 本质是一个高阶组件
    • 自定义配置 npm run eject
    • npm install babel-plugin-transform-decorators-legacy --save 支持注解
    • package.json里babel加上plugins配置
  • 复杂redux应用,多个reducer,用combineReducers合并

react-route

简单介绍

  • 官方推荐路由库,4是最新版本,和之前版本不兼容,浏览器和 RN 均兼容
  • 开发单页应用必备,践行路由即组件的概念
  • 核心概念:动态路由,router,link,switch
  • npm install react-router-dom --save
  • Router4 使用 react-router-dom 作为浏览器前端的路由
  • 忘记 router2 的内容,拥抱最新 router4 吧

入门组件

  • BrowerRouter 包裹整个应用
    <BrowserRouter>
      <App></App>
    </BrowserRouter>
    
  • Router 路由对应渲染的组件,可嵌套,exact属性表示严格匹配
    <Switch>
      <Route path='/login' component={Login}></Route>
      <Route path='/register' component={Register}></Route>
      <Route path='/chat/:user' component={Chat}></Route>
      <Route component={Dashboard}></Route>
    </Switch>
    
  • Link 跳转专用
    <Link to={`${match.url}`}>跳转</Link>
    

其他组件

  • url参数,Route组件参数可用冒号标识参数,路由组件给自己的组件添加了很多属性,打印props可以看到,eg:this.props.match.params.paramname读取值
  • this.props.match
    • url是实际的路由
    • path是我们定义的路由,有可能有变量
  • Redirect组件跳转
    <Redirect to={this.props.redirectTo}></Redirect>
    
  • Switch只渲染命中的第一个子Route组件
  • 使用push方法进行跳转
    this.props.history.push('/register');
    

经观察DOM,react-router 不同于 angular 路由,在 DOM 结构上,只要不是当前页面显示的内容,在DOM中直接被移除,不存在缓存的概念(可能有?还没学到?)

对于非路由组件,如果需要使用路由组件的相关功能,即将路由的相关函数和属性挂载到组件的props下, 使用withRouter,在配置了装饰器的情况下,在class前@withRouter即可。

Socket.io

Socket.io 是什么

  • 基于事件的实时双向通信库
  • 基于websocket协议

Socket.io(websocket) 与 ajax 区别

  • ajax基于http协议,单向,实时获取数据只能轮询
  • socket.io 基于websocket双向通信协议,后端可以主动推送数据
  • 现代浏览器均支持websocket
  • npm install socket.io socket.io-client --save

Socket.io 后端API

  • io = require('socket.io')(http)
  • io.on
  • io.emit

Socket.io 前端API

  • import io from 'socket.io-client'
  • io.on
  • io.emit

项目架构

文件结构和规范

  • src前端源码
  • server后端express目录
  • 根据功能文件夹
    • 通用组件component
    • 业务组件container
    • redux文件redux

前后端联调

  • axios发送异步请求
  • npm install axios --save
  • 不同域?使用proxy配置转发,package.json "proxy":"http://localhost:9093"
  • 单独使用直接在组件中componentDidMount即可
  • axios拦截器,统一loading处理,axios.interceptors
  • redux使用异步数据,渲染页面

elint

  • package.json中默认继承react-app,这是create-react-app默认的配置
  • 保证团队代码风格的统一
  • 自定义配置
    "eslintConfig": {
    "extends": "react-app",
    "rules":{
      "eqeqeq":["off"]
    }
    }
    

性能优化

React 原理

  • 虚拟 DOM
    • beforeTree 和 afterTree 平级对比,而不是递归对比
    • 避免跨 DOM 层级去操作数据,这样虚拟 DOM 无法优化
    • 如何做 diff,打 patch,updateChildren,需深入
  • 组件初始化:constuctor-》componentWillMount-》render-》componentDidMount
  • 生命周期,shouldComponentUpdate(nextProps,nextState)(可优化处,达到最少渲染次数)
  • 组件更新三种策略
    • setState(异步队列更新):依次调用shouldComponentUpdate-》componentWillUpdate-》render-》componentDidUpdate
    • 父组件renders:componentWillReceiveProps-》shouldComponentUpdate-》componentWillUpdate-》render-》componentDidUpdate
    • forceUpdate:用的较少,不会调用shouldComponentUpdate

React 如何做性能检测

  • 路径添加 ?react_perf
  • 使用chrome自带的performance
    • 点击record开启,做出我们想要检测的操作之后,点击stop
    • 主要查看User Timing

React 组件(间)性能优化

  • 属性传递优化
  • 多组件间优化
  • key
    • 删除、新增、移动
    • 如何确定是同一个元素呢,就是通过key
    • 使用数组的index,除了取消warning,没有其他作用,因为index是变化的 Redux 性能优化
  • Redux state到组件显示数据转换,有性能优化空间,利用纯函数的特点,做缓存。
  • reselect库优化redux
    • npm install reselect --save
    • 函数记忆,减少重计算 React 同构
  • 首屏服务端渲染SSR
  • 路由懒加载

纯函数的特点;稳定输入,稳定输出,因此可以做缓存

React 15以后,新增PureComponent,帮你解决了手写shouldComponentUpdate的烦恼。

  • 此时性能检测:直接没有渲染,比我们定制的更棒
  • 如果你的组件只是根据你传进来的值进行渲染,并没有内部的状态,可以直接继承PureComponent即可

immutablejs

immutablejs存在的意义和使用

  • 递归对比,复杂度太高,不可接受
  • React妥协,只做浅对比,这也是为什么我们在做 redux 和 state 的时候,建议不要那么深层次嵌套
  • facebook 官方库,在 JS 里引出一个不可变的数据结构
    • 数据结构一旦创立不能修改,只能生成新的数据结构
    • 我们直接用等号就可以判断两个数据结构是不是相等,这对shouldComponentUpdate而言,简直就是利器
  • npm install immutable --save
  • Map
  • 优点:
    • 节省内存,数据不需要修改
    • 并发安全
    • 降低了可变带来的复杂度,共享可变状态是万恶之源
    • 便于比较复杂数据,定制shouldComponentUpdate方便
    • 时间旅行功能
    • 拥抱函数式编程,纯函数
  • 缺点
    • 学习成本
    • 库的大小(替代方案:seamless-immutable)
    • 对现有项目入侵太严重(新项目使用,老项目值得好好评估)

SSR

SSR的发展:

  • 全称:server side render
  • 传统服务端渲染,JSP、smaty、jinja2
    • 前后端一体
    • 后端模板+数据=》html 给浏览器
    • 首屏块,每次获取数据都会刷新页面
  • 浏览器渲染,ajax 获取数据,前端拼接页面
    • 后端仅提供静态资源和接口
    • 前端写模板,渲染,MVVM 大行其道
    • 异步获取数据,无刷新
    • 单页应用,页面跳转也不刷新,体验好
    • 首屏较慢,没办法做 SEO,对搜索引擎不友好
    • jQuery 时代 underscore
  • 前后同构,首屏服务端渲染
    • node 在服务端解析首屏模板
    • 页面渲染逻辑就不需要了,只需要做注水操作(事件响应等)
    • React 支持 SSR

React同构API

  • RenderToString 和 RenderToStaticMarkUp
  • React16 新出的 RenderToNodeStream,性能更好
    • RenderToString 解析为字符串
    • RenderToNodeStream 解析为可读的字节流对象
    • 官方说速度会快3倍左右
  • React16 里,客户端 ReactDom.hydrate 取代 ReactDom.render

项目SSR具体步骤

进阶

mini-redux

核心内容,具体看源码看github

  • createStore(reducer, enhancer)
    • 返回 getState, subscribe, dispatch 函数
    • 中间件的处理
  • applyMiddleware(...middlewares)
    1. 执行createStore,得到初始的store
    2. 创建midApi,在redux中间件中需要dispatch和getState,依次执行中间件的得到middlewareChain
    3. compose 多个中间件,传入disptach执行,得到新的dispatch
    4. 返回新的store,主要是包装了dispatch
  • bindActionCreators(creators, dispatch)
    • 主要给 mini-react-redux 使用,目的使mapDispatchToProps具备自动dispatch功能

mini-react-redux

React Context API

Context API 在这里是我们实现mini-react的关键,因此我们先熟悉 Context API的基本使用。

  • 严格类型要求,否则无法获取数据
  • 通过将 childContextTypes 和 getChildContext 添加到父组件(context 提供者),React 自动地向下传递信息,并且子树中的任何组件都可以通过定义 contextTypes 去访问它。如果 contextTypes 没有定义, context 将是一个空对象。

propTypes:定义组件内部属性的数据格式要求,引入Context API之后,可千万不要弄混了哟。

Demo

class NavBar extends React.Component {
    static contextTypes = {
        user: PropTypes.string
    }
    render() {
        console.log(this.context);
        return (
            <div>{this.context.user}的导航栏</div>
        )
    }
}

class Page extends React.Component {
    static childContextTypes = {
        user: PropTypes.string
    }
    constructor(props) {
        super(props);
        this.state = { user: 'sky' }
    }
    getChildContext() {
        return this.state;
    }
    render() {
        return (
            <div>
                <p>我是{this.state.user}</p>
                <SideBar></SideBar>
            </div>
        )
    }
}
export default Page;

如果 contextTypes 在组件中定义,下列的生命周期方法将接受一个额外的参数, context 对象:

  • constructor(props, context)
  • componentWillReceiveProps(nextProps, nextContext)
  • shouldComponentUpdate(nextProps, nextState, nextContext)
  • componentWillUpdate(nextProps, nextState, nextContext)
  • componentDidUpdate(prevProps, prevState, prevContext)

从 React 16 开始, componentDidUpdate 不再接收 prevContext 。

在无状态的函数式组件中引用 Context

无状态的函数式组件也可以引用 context , 如果 contextTypes 作为函数的属性被定义。

const PropTypes = require('prop-types');

const Button = ({children}, context) =>
  <button style={{'{{'}}background: context.color}}>
    {children}
  </button>;

Button.contextTypes = {color: PropTypes.string};

核心实现

我们需要一个Provider组件用来包裹整个应用,然后就是实现connect高阶组件。

Provider组件

  1. 通过props得到store,实现childContextTypes属性和getChildContext方法,将store放入context中
  2. render 返回this.props.children即可

connect高阶组件

  1. 实现contextTypes属性,使得可以通过this.context得到store
  2. 在state上注册props,用来存储最终的props
  3. 在componentDidMount中,通过store.subscribe方法注册update方法
  4. 主要处理mapStateToProps函数和mapDispatchToProps对象(redux的bindActionCreators)
  5. 构建新的props
  6. render 返回组件,将会state.props统统交给组件

中间件

mini-redux-thunk

典型的高阶函数,我们依次需要初始的dispatch,next,action参数,源代码为:

const thunk = ({ dispatch, getState }) => next => action => {
    // 如果不符合我们的要求,直接调用下一个中间件,使用next
    // 如果符合我们的要求,需要重新dispatch,调用dispatch即可
    if (typeof action === 'function') {
        return action(dispatch, getState)
    }
    // 默认什么都没干
    return next(action)
}

export default thunk;

compose 函数

compose 函数组合,是实现redux中间件的关键,因此我们先讲讲compose的简单实现和一个例子

function compose(...funcs){
    if(funcs.length === 0 ){
        return arg => arg
    }
    if(funcs.length === 1){
        return funcs[0];
    }
    return funcs.reduce((ret,item)=> (...args) => ret(item(...args)))
}

// 仅支持打印原本数据
function originFun(target){
    console.log(target);
    return target;
}

// 我们增强它,如果是数组,大声说出来
function middlewareArray(origin){
    return function(next){
        console.log(next);
        return function(target){
            console.log('middlewareArray');
            if(Array.isArray(target)){
                console.log('I am a Array');
                return origin(target);
            }
            return next(target);
        }
    }
}

// 我们增强它,如果是数字,大声说出来
function middlewareNumber(origin){
    return function(next){
        console.log(next);        
        return function(target){
            console.log('middlewareNumber');
            if(typeof target === 'number'){
                console.log('I am a Number');
                return origin(target);
            }
            return next(target);
        }
    }
}

// origin表示最原始的,next一开始也是原始的,然后就是被处理过的啦
var newOrigin = compose(middlewareNumber(originFun),middlewareArray(originFun))(originFun);
newOrigin(123);

其他

vscode

react 开发环境搭建

  1. 格式化问题
    • vscode shift+option+f(mac)
    • editor.formatOnSave为true启用保存格式化
  2. JSX标签自动补齐
    • emmet.triggerExpansionOnTab为true
    • 有时候会失效,继续设置"emmet.includeLanguages": {"javascript": "javascriptreact"}
  3. 启用ESLint校验
    • 安装ESLint插件
    • 修改配置eslint.autoFixOnSave为ture

React 动画

  • CSS动画
    • 官方解决方案:ReactCSSTransitionGroup
  • JS动画
  • Ant Motion
    • 进出场动画:npm install rc-queue-anim --save

打包编译

  • npm run build
  • 编译打包后,生成build目录
  • express中间件,拦截路由,手动渲染index.html
  • build设置为静态资源地址

npm scripts

  • 有没有觉得奇怪,为什么命令中,可以直接npm start,其余命令却需要使用npm run xxx呢
  • 四个常用的 npm 脚本有简写形式
    • npm start是npm run start
    • npm stop是npm run stop的简写
    • npm test是npm run test的简写
    • npm restart是npm run stop && npm run restart && npm run start的简写
  • 更多npm scripts 使用指南

图片路径

组件内引用图片资源

  1. 直接 import
  2. 使用 require:webpack 就会识别到,打包上线的时候就会修正路径
  3. 图片会经过 webpack 处理

引入asset-require-hook来识别图片资源时候,默认对小于8K的图片转换成base64字符串,大于8k的图片转换成路径引用

线上发布:

  • 使用 git 部署到服务器 npm install 一直报错 killed 导致无法完成
  • 路径问题,mongodb 路径和 websocket服务路径
    • 设置mongo用户访问权限相关时,导致我博客挂了,找不到原因,奇怪的事,我重启一下服务就好了,好像是因为中途数据库断开连接的问题
  • forever 如何运行 npm 命令
    • 安装npm install babel-register --save
    • 添加server-wrapper.js
    • babel-preset-react-app必须添加NODE_ENV等于development、test、production否则报错
    • 运行 NODE_ENV=test forever start server/server-wrapper.js
  • 端口被占用
    • netstat -tunlp|grep port
    • kill pid


留言