react--随笔3

扩展

Immutable.js



typescript

搭建环境

create-react-app 目录 --template typescript 

统一变化

  • 所有用到jsx语法的文件都需要以tsx后缀命名
  • 使用组件声明时的Component泛型参数声明,来代替PropTypes!
  • 全局变量或者自定义的window对象属性,统一在项目根下的global.d.ts中进行声明定义
  • 对于项目中常用到的接口数据对象,在types/目录下定义好其结构化类型声明

react类型

RouteComponentProps

RouteComponentProps

RouteChildrenProps

HTMLDivElement

match

React.FC

React.FunctionComponent

React.ReactNode

ComponentType

JSX.Element

Dispatch

AxiosRequestConfig

AxiosPromise

类组件

创建组件

export default class 组件名 extends React.Component{}
export default class Comp3 extends React.Component<{ //内联类型注解
  value: string; 
  onChange: (value: string ) => void 
}, {}> {}
export default class 组件名 extends React.Component<{}, {}>{} //组件没有props和状态时

IProps,IState接口类型需要定义,可以定义在组件文件内部,或者types目录

类型约束

export interface List {//通用,可以丢到外部,也可以在外部定义,推荐`types/`目录下
  readonly id: number;
  name: string;
}
type IProps = { //未export 不通用
  readonly id: number;
  title: string;
  num?: number;
  arr?: string[]
}
type IState = {
  msg1: string;
  msg2: number;
  list: List[]
}

组件状态

export default class 组件名 extends React.Component{
	state: Readonly = initState;//state不建议通过实例属性修改,作为只读定义
}

initState 可定义到组件外部,也可定义在内部

let initState:IState = {
  msg1: 'xx',
  msg2: 12,
  list: [
    { id: 1, name: 'alex' },
    { id: 2, name: 'alex2' },
    { id: 2, name: 'alex3' },
  ]
};

props属性

类型约束内部确定只读,必传,可选特性,默认值在类属性defaultProps设定

type IProps = { //未export 不通用
  readonly id: number;//只读, props理论都应该是只读
  title: string;//必传
  num?: number;//可选
  arr?: string[]
}
export default class 组件名 extends React.Component{
	
  //props默认值
  static defaultProps={
    num:0
  }
}

事件

const initialState = { clicksCount: 0 };//先定义值

//再使用typeof推断类型,并设置只读,限定this.state修改
type TState = Readonly;
                      
class Counter extends Component<{}, TState>{
  readonly state: TState = initialState;//因为 React 不推荐直接更新 state 及其属性
  render() {
    const { clicksCount } = this.state;
    return (
      

事件

{clicksCount}
) } // private handlerIncrement=()=>this.setState({clicksCount:this.state.clicksCount+1}) // private handlerDecrement=()=>this.setState({clicksCount:this.state.clicksCount-1}) private handlerIncrement = () => this.setState(increment) private handlerDecrement = () => this.setState(decrement) } //独立纯函数,编译单独测试 const increment = (prevState: State) => ({ clicksCount: prevState.clicksCount + 1 }) const decrement = (prevState: State) => ({ clicksCount: prevState.clicksCount - 1 })

函数式组件

定义组件

使用 React.FunctionComponent 接口定义函数组件

type Props = {
  foo: string;
};

const MyComponent: React.FunctionComponent = props => {
  return {props.foo};
};
export {MyComponent}

使用 React.FC 别名定义函数组件

interface IProps {
  readonly id?:number;
  title?:string;
  num?:number;
  arr?:string[]
}

type IProps2=Readonly;//类型映射

//函数式组件
const Footer: React.FC = (props) => {
  // props.num=2; //error 类型约束props为只读
  // props.title='2323';//error
  return (
    
footer
) } export default Footer

事件,props

事件函数通过props传入

type TProps = {
  onClick(e: MouseEvent): void//必传
  text?: string//可选
}

//props默认值在函数接收参数时设定,handleClick为对象别名
const Button: FC = ({ onClick: handleClick, text = '按钮' }: TProps) => (
  <>
    

无状态组件

);

组件注入

通过props或直接嵌套的方式,向组件注入一些可变的元素

jsx节点

注入的元素有:string,number,boolean,ReactElement

举例:Header} body={12} />

interface Iprops {
  header?: React.ReactNode;//jsx节点类型 string,number,boolean,ReactElement
  body: ReactNode;//类型需要导入,来自react包
}

//! 代表排除null
class Comp2 extends React.Component {
  render() {
    return (
      <>
        
---------接收可渲染的内容start--------
{this.props!.header} {this.props.body}
---------接收可渲染的内容end--------
); } }
children嵌套

调用组件时,插入到组件的内容<组件>内容

type AuthProps = {
  children?: JSX.Element;//设定children类型,可选
  [propName:string]:any//props可以接受其他任何值
}
const authState = { show: false }//先赋值
type AuthState = Readonly//后推断类型

class Auth extends Component{
  static readonly defaultProps: AuthProps = {title:'bmw'}
  readonly state: AuthState = authState
  render() {
    const { children } = this.props
    return (
    	<>
      	
组件自身内容
{children && children}
组件自身内容
) } }
注入组件

类似于

type AuthProps = {
  component?: ComponentType;//设定要接受的组件类型ComponentType
}
const authState = { show: false }
type AuthState = Readonly

class Auth extends Component{
  static readonly defaultProps: AuthProps = {title:'bmw'}
  readonly state: AuthState = authState
  render() {
    const { component: InjectedComponent } = this.props //InjectedComponent字面量别名
    
    return (
    	<>
      	

组件本身

{/* 调用通过props传入的组件 */} {InjectedComponent && } ) } }
注入render属性

给调用的组件传递render函数,指定被调用组件的渲染内容

举例:

 (
  
render后的内容
)} />

实现:

type AuthProps = {
  render?: () => JSX.Element;
  [propName:string]:any
}
const authState = { show: false }
type AuthState = Readonly

class Auth extends Component{
  static readonly defaultProps: AuthProps = {title:'bmw'}
  readonly state: AuthState = authState
  render() {
    const { render } = this.props
    if (render) {
      return render()//调用传入渲染函数,按照外部要求渲染
    }
    return (
    	
原本内容
) } }

ref在类组件

引用渲染完成后的元素

方式1
import React from 'react';

export default class Comp3 extends React.Component<{ //内联类型注解
  value: string; 
  onChange: (value: string ) => void 
}, {}> {
	
  //使用 ref 和 null 的联合类型,并且在回调函数中初始化他
  input: HTMLInputElement | null = null;
  
  render() {
    return (
      <>
        

refs

this.input = el} value={this.props.value} onChange={e => this.props.onChange(e.target.value)} /> ); } componentDidMount(){ this.input != null && this.input.focus() //获取焦点 } }
方式2
import React,{createRef} from 'react';

export default class Comp3 extends React.Component<{ //内联类型注解
  value: string; 
  onChange: (value: string ) => void 
}, {}> {

  // 使用createRef函数,返回ref对象,并指定类型,作为实例
  input = createRef()
  
	render() {
    return (
      <>
        

refs

this.props.onChange(e.target.value)} /> ); } componentDidMount(){ this.input.current!.focus() } }

数据交互 axios

反向代理

在src目录下创建setupProxy.js

const proxy = require('http-proxy-middleware'); //需要安装中间件

module.exports = function(app) {
  app.use(
    proxy("/api", {
      target: 'http://localhost:3001',
      changeOrigin: true
    })
  );
  app.use(
    proxy("/mock", {
      target: 'http://localhost:3333',
      changeOrigin: true
    })
  )

};
axios 二次封装

在plugins目录创建axios.ts

import axios from 'axios';

export interface IUser {//通用,可在外部定义,或外部使用
  err:number,
  data:any,
  token:string
}
type TUser = Partial<IUser> & string | null //映射 交叉 联合

// 添加一个请求的拦截
axios.interceptors.request.use((config) => {

  //1抓取本地token,携带在请求头里
  let user:TUser = window.localStorage.getItem('user');
  user = user ? JSON.parse(user) : '';
  config.headers={'token': user!.token}

  //显示loading...

  return config;//返回请求

}, function(error) {
  // 请求错误时做点事
  return Promise.reject(error);
});

//添加一个响应拦截
axios.interceptors.response.use(function(response) {

  // res.data ~~ {err:1,msg:xx,data:{}} ~~ response.data

  //token过期: 返回值2,当前路由不是login时跳转,并传递当前路径,登录后可以有参考原路跳回
  if (response.data.err === 2 && !window.location.href.includes('/login')) {
    window.location.href='http://localhost:3000/login?path='+window.location.pathname
  }

  return response;

}, function(error) {
  return Promise.reject(error);
});

declare global { //定义到全局  也可以定义到src/global.d.ts
  interface Window {//给window接口添加axios方法函数
    axios(config: AxiosRequestConfig): AxiosPromise<any>
  }
}

window.axios = axios;  //希望全局使用axios ,

export default axios;

数据交互
import React from 'react';
// import axios from '../plugins/axios';

export interface IListItem {//推荐定义到src/types目录下
  _id: string, title: string, des: string, time: number
}


//数据交互
export default class Comp4 extends React.Component<{},{}> {
  readonly state:{
    list:Array
  }={
    list:[]
  }
  render() {
    let {list}=this.state
    return (
      <>
        

comp4-数据交互

{ list.map((item:IListItem)=>(
  • {item.title}/{item.time}
  • )) } ); } componentDidMount(){ //window.axios({ axios({//需要引入plugins/axios url:'/api/home' }).then( ({data:{data:list}})=>this.setState({list}) ) } }

    路由

    主入口.tsx

    import {BrowserRouter as Router,Route} from 'react-router-dom'
    
    ReactDOM.render(
        
          
        
      , 
      // document.getElementById('root') as HTMLElement
      document.getElementById('root')! //移除null和undefined
    );
    

    根组件.tsx

    列表页.tsx

     
  • {item.title}/{item.time}
  • 详情页.tsx,解决params._id 不存在的问题

    import React from 'react'
    import {RouteComponentProps,match} from 'react-router-dom';
    import qs from 'qs';//类似query-string
    
    export interface IDetail {
      title: string;
      des: string;
      time: number;
      detail: {
        auth: string;
        content: string;
        auth_icon: string;
      }
    }
    
    type TDetail = {
      err: number;
      msg: string;
      data: Partial;//可选映射
    };
    
    //params._id 不存在
    
    //解决方案1
    type TProps={
      match: match<{_id?: string}>//交叉一个属性到RouteComponentProps类型
    } & RouteComponentProps
    
    //解决方案2
    type ParamsInfo = {//作为 RouteComponentProps的泛型传入,定义params的内容
      _id:string
    }
    type TProps2=RouteComponentProps;
    
    export default class Detail extends React.Component{
      readonly state: TDetail = {
        err: 1,
        msg: '失败',
        data: {}
      }
      componentDidMount() {
        let dataName=qs.parse(this.props.location.search,{ignoreQueryPrefix:true}).dataName;
        let _id=this.props.match.params._id||null;
        window.axios({
          url:`/api/${dataName}/${_id}`
        }).then(
          ({data:{err,msg,data}})=>this.setState({err,msg,data})
        )
      }
      render() {
        let { err, data } = this.state;
    
        return (
          <>
            {
              err === 0 ? (
                

    detail

    {data.title}/{data.detail?.auth}
    ) : (
    骨架屏
    ) } ) } }

    函数式组件,接受路由上下文的类型约束

    const Login: React.FC = ({ history, match, location }) => {}
    

    需要规定FC别名的泛型约束RouteChildrenProps, 来自react-router-dom包

    状态管理

    安装:

    yarn add redux react-redux @types/react-redux -S
    
    

    定义类型:src/types

    //public type
    export interface IListItem {
      _id: string, title: string, des: string, time: number
    }
    
    //store type
    export interface IStoreState {
      bNav: boolean;
      bFoot: boolean;
      bLoading: false;
      home: IListItem[];
      follow: Array<IListItem>;
      user: {
        err: number;
        msg: string;
      },
    
      count:number;
      test:number;
    }
    
    export type StoreState = Partial<Readonly<IStoreState>>
    
    
    //action type
    export type TActionCount = {
      type: string;
      payload?: number
    }
    

    定义提交类型:src/store/const.ts

    // 定义增加 state 类型常量
    export const INCREMENT = "INCREMENT";
    // 定义减少 state 类型常量
    export const DECREMENT = "DECREMENT";
    
    
    

    定义action: src/store/actions

    import { DECREMENT, INCREMENT } from '../const'
    import { TActionCount } from '../../types'
    import { Dispatch } from 'redux';
    
    // 增加 state 次数的方法  同步
    export const increment = (): TActionCount => ({
      type: INCREMENT,
    })
    
    // 减少 state 次数的方法 异步
    export const decrement = (arg: any): any => (dispatch: Dispatch): Promise<any> => new Promise((resolve, reject) => {
      setTimeout(() => {//axios走起
        dispatch({ type: DECREMENT })
        resolve('异步actions发回来的回执')
      }, 1000)
    })
    

    定义reducers: src/store/reducers

    import { combineReducers } from 'redux'
    
    import {count} from './count'
    import {other} from './other'
    
    // combineReducers 可以吧store变成一个对象来组合reducer
    //combineReducers(对象)  对象{key:value} key=state.key value=reducer函数
    const rootReducer = combineReducers({
     count,//state.count: count的reducer函数
     other
    })
    
    export default rootReducer;
    

    定义count: src/store/reducers/count

    import { DECREMENT, INCREMENT } from '../const';
    import { TActionCount } from '../../types';
    
    //参数state 只代表state.count的值 , 一定要有初始值
    export function count(state:number = 0, {type,payload}: TActionCount): number {
      switch (type) {
        case INCREMENT:
          return payload ? state + payload : state + 1;
        case DECREMENT:
          return state - 1
        default:
          return state
      }
    }
    

    定义store实例: src/plugins/redux

    import { createStore,applyMiddleware} from 'redux';
    import { composeWithDevTools } from 'redux-devtools-extension'//开启调试工具
    import thunk from 'redux-thunk'//改装dispatch接受函数
    import reducer from '../store/reducers'; 
    
    // 1、创建 store  initState是可选参数
    const store = createStore(reducer,composeWithDevTools(applyMiddleware(thunk)));
    
    export default store;
    

    主入口引入store:

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import App from './layouts/App';
    
    import {BrowserRouter as Router,Route} from 'react-router-dom'
    
    
    import { Provider } from 'react-redux';
    import store from './plugins/redux'
    ReactDOM.render(
      
        
          
        
      
      , 
      // document.getElementById('root') as HTMLElement
      document.getElementById('root')! //移除null和undefined
    );
    

    组件接入redux使用:

    import React from 'react';
    import { connect } from 'react-redux';
    import { Dispatch } from 'redux';
    
    import { StoreState } from '../types';
    import { INCREMENT } from '../store/const';
    import { decrement } from '../store/actions';
    
    
    // 创建类型接口
    export interface IProps {
      count?: number;
      test?: number;
      onIncrement: () => void,
      onDecrement: () => void
    }
    
    // 使用接口代替 PropTypes 进行类型校验
    class Counter extends React.PureComponent {
      public render() {
        const { count, onIncrement, onDecrement } = this.props;
        return (
          <>
            

    redux+react-redux+react-thunk

    {count}

    ) } } // 将 reducer 中的状态插入到组件的 props 中 // 下面是单个reducer的时候,多个的时候需要选传入哪个reducer // const { test, count } = state // const mapStateToProps = (state: StoreState): StoreState => ({ const mapStateToProps = ({count,other}: StoreState): StoreState => ({ // count:state.count count }) // 将 对应action 插入到组件的 props 中 const mapDispatchToProps = (dispatch: Dispatch) => ({ onDecrement: () => dispatch(decrement('组件发出的参数')).then((res:string)=>console.log(res)), onIncrement: () => dispatch({type:INCREMENT,payload:2}) }) // 使用 connect 高阶组件对 Counter 进行包裹 export default connect(mapStateToProps, mapDispatchToProps)(Counter);

    hooks

    import React, { useState, useEffect, useRef } from 'react'
    
    interface Item{
      id?:number;
      title?:string;
    }
    const Reg: React.FC = () => {
      //只有在没有初始值的情况下才需要加入类型限制,因为有初始值时可以推断出实际状态的类型。
      const [msg, setMsg] = useState('数据1');//有初始值,会类型推断 √
      const [msg2, setMsg2] = useState();//没有初始值,推断为any
      const [msg3, setMsg3] = useState(0);//手动指定类型和初始值
      const [msg4, setMsg4] = useState({id:1});//手动指定类型
      const [msg5, setMsg5] = useState([]);//手动指定类型
    
      // const box = useRef(null)//有时设置引用可能会在稍后的时间点发生
      const box = useRef(null)//使用 useRef 时需要更加明确 被引用的类型
    
      useEffect(() => {
        console.log('didMount');
        box.current!.style.background='red';
        return () => {
          console.log('unmount');
        };
      }, []);
    
      return (
        <>
          

    hooks

    msg:{msg}
    msg2:{msg2}
    msg3:{msg3+1}
    {/* ? 代表对象存在,才去访问子key */}
    msg4:{msg4?.id}/{msg4?.title}
    msg5: { msg5?.map((val,index)=>(
  • {val}
  • )) }
    box
    ) } export default Reg;

    umi2

    官网 项目

    创建项目

    mkdir project
    cd project
    yarn create umi
    

    项目结构

    |-public 本地数据资源
    |-mock umi支持mock数据,无需代理
    |-src
      |-assets 开发资源
      |-compoennts 通用组件
      |-layouts 为根布局,根据不同的路由选择return不同的布局,可以嵌套有一些和布局相关的组件
      |-pages 页面 约定式路由的页面
      |-plugins 子有的插件配置 如axios
      |-routes 授权路由
      |-app.js 运行时的配置文件  对等 react的index.js
      |- global.css 全局样式
    |-umirc 编译时配置
    

    资源指向

    • 支持@指向src别名
    • 相对指向 src
    • 绝对指向 public
    • img标签 指向public或者服务器
    • 静态图片: import | require |
    • 数据图片: 绝对指向public | 服务器

    路由

    路由使用约定式,对齐nuxt,也可配置umirc后使用配置型路由,路由组件同react-router解构来自umiimport {NavLink} from 'umi'

    约定式路由

    |-pages
      |-index.js   "/" 路由 页面
      |-index/index.js "/" 路由 页面
      
      |-goods.js  // /goods路由
      |-goods/index.js  // /goods路由页
        //goods 路由的默认页 return null 代表没有默认页
      |-goods/$id.js  // /goods/1 路由页 
      |-goods/$id$.js  // /goods 路由 goods下没有index 时 展示了 /goods 或 /goods/id,代表id可选
      |-goods/_layout.js  // /goods 路由页 有自己的展示区 {props.children} 不然会在父的展示区展示
    
      |-goods/category.js   // /goods/category 路由
      |-goods/category/index.js // /goods/category 路由
    
      |-404.js 生产模式下的404页,开发模式默认umi的
      //document.ejs 浏览器模板页 没有umi自动生成,有取当前模板结构
    

    路由跳转

    声明式
    商品 002
    
    编程式
    import router from 'umi/router';
    					
    router.push('/login')
    
    router.push({
      pathname:'/goods/1',
      query:{a:11,b:22}
    })
    // search:'a=111&b=222' 如果传递了search 会覆盖query,query只是对search的封装引用了search的值
    
    props.history.xx() //可用
    

    传接参

    props.match.params
    props.location.query 返回对象
    
    

    授权路由

    前置
    //组件内部守卫, 在组件文件最上方
    
    /*
    * title: reg Page
    * Routes:
    *   - ./src/routes/Auth.js
    * */
    
    后置
    //同react
     {
        return window.confirm(`确认要去向 ${location.pathname}?`);
      }}
    />
    

    数据交互

    自带mock,无需代理,其他数据需要代理,本地数据放在public

    app.js 配置

    在出错时显示个 message 提示用户,在加载和路由切换时显示个 loading,页面载入完成时请求后端,根据响应动态修改路由,引入一些插件配置模块,在运行时带入这些配置

    配置思想

    对外导出一堆umi内部可识别的函数 来完成配置

    export function render(oldRender) {
        渲染应用之前做权限校验,不通过则跳转到登录页
        oldRender() 渲染应用的函数
    }
    
    export function onRouteChange({ location, routes, action }) {
      初始加载和路由切换时的逻辑,用于路由监听, action 是路由切换方式如:push
    }
    
    export function rootContainer(container) {
      封装 root container  外层有个 Provider 要包裹 的场景 必须要有返回值,无需是可以不写这个函数
      // const DvaContainer = require('@tmp/DvaContainer').default;
      return React.createElement(DvaContainer, null, container);
    }
    
    export function modifyRouteProps(props, { route }) {
      修改传给路由组件的 props , 所有组件都中
        props,Object,原始 props
        route,Object,当前路由配置
        return { ...props, 混入后的key: 值 };
    }
    

    umirc 配置

    配置编译环境 umirc 无需重启

    export default {
    
      history:'hash' 路由模式 默认历史记录
    
      publicPath: "/public/" 数据资源在非根目录或cdn时使用, 必须 绝对路径 + /结尾 影响打包后的位置会指向public
    
      disableCSSModules: false 	关闭css模块化 默认开启,推荐开启
    
      cssModulesExcludes:['index.css','login.css'] 指定项目目录下的文件不走 css modules 不支持scss
    
      sass: {}	支持scss 需要安装 sass-loader node-sass
    
      mountElementId:'app' 	指定 react app 渲染到的 HTML 元素 id。
    
      proxy: {
        '/api': {
          target: 'http://localhost:3001',
          "changeOrigin": true,
          // pathRewrite: {'^/api' : ''}
        },
        '/douban': {
          target: 'https://douban.uieee.com',
          "changeOrigin": true,
          pathRewrite: {'^/douban' : ''},
          secure: false //接受https的代理
        }
      },
    
      routes: [ //使用手动配置路由,约定式路由失效
        {
          path: '/',
          component: '../layouts/index',
          routes: [
            { path: '/', component: '../pages/index.js' },
            { path: '/users/', component: '../pages/users/index.js' },
            { path: '/users/list', component: '../pages/users/list.js' },
            { path: '/users/:id', component: '../pages/users/$id.js' },
          ]
        }
      ],
    
      plugins: [ 插件配置
        ['umi-plugin-react', {
          antd: false, 是否开启antd 需要安装依赖
          dva: false, 是否器dva支持 需要安装依赖
    
          dynamicImport: {//按需加载 生产环境下有效果
            webpackChunkName: true,//实现有意义的异步文件名
            loadingComponent: './components/Loading.js',//指定加载时的loading组件路径
          },
    
          title: 'umitest', 开启 title 插件
    
          routes: {
            exclude: [	用于忽略某些路由,比如使用 dva 后,通常需要忽略 models、components、services 等目录
              /components\//,
            ],
          },
        }],
      ],
    }
    

    umi3

    官网

    使用react开发,可扩展的企业级前端应用框架,让react开发高度可配置(合并式,无需触碰webpack底层),支持各种功能扩展和业务需求,可替换next完成服务端渲染

    Umi 如何工作

    把大家常用的技术栈进行整理,收敛到一起,让大家只用 Umi 就可以完整 80% 的日常工作。CRA不支持配置(合并式),不是框架,但umi是,且支持配置,并内置插件的方式整合开发者遇到的一些常规业务问题(如 antd、dva 的深度整合,比如国际化、权限、数据流、配置式路由、补丁方案、自动化 external 方面)

    技术收敛

    img

    创建项目

    //首先得有 node,并确保 node 版本是 10.13 或以上
    mkdir project
    cd project
    yarn create @umijs/umi-app
    yarn 安装依赖
    yarn start 启动开发
    yarn build 打包构建
    

    项目结构

    .
    ├── package.json //插件和插件集 @umijs/ 开头的依赖会被自动注册为插件或插件集。
    ├── .umirc.ts //配置文件,包含 umi 内置功能和插件的配置
    ├── .env //环境变量
    ├── dist //打包后
    ├── mock //mock 文件,此目录下所有 js 和 ts 文件会被解析为 mock 
    ├── public //静态资源
    └── src
        ├── .umi //临时文件目录 忽视
        ├── layouts/index.tsx //布局组件
        ├── pages // 页面级别路由组件
            ├── index.less
            └── index.tsx
        └── app.ts //运行时配置文件 扩展运行时的能力
    		├── global.css // 全局样式,如果存在此文件,会被自动引入到入口文件最前面 无效
    

    不希望类型检查,可以更名为jsx或者js

    样式和资源指向

    • 支持@别名指向src
    • css 里面~@执向src
    • 相对 根在 src
    • 绝对根在 public
    • 静态图片引入: import | require | 指向src
    • 数据图片引入: 指向public | 服务器
    • src/global.css 为全局样式
    • 当做 CSS Modules 用时,Umi 会自动识别 如: import styles from './foo.css'

    路由

    路由使用约定式,对齐nuxt,也可配置umirc后使用配置型路由,路由组件同react-router解构来自umi import {NavLink} from 'umi'

    约定式路由

    也叫文件路由,不需要手写配置,没有 routes 配置,Umi 会进入约定式路由模式,然后分析 src/pages 目录

    |-pages
      |-index.tsx   "/" 路由 页面
      |-index/index.tsx "/" 路由 页面
      
      |-goods.tsx  // /goods路由
      |-goods/index.tsx  // /goods路由页
        //goods 路由的默认页 return null 代表没有默认页
      |-goods/[id].tsx  // /goods/1 路由页 
      |-goods/_layout.tsx  // /goods 路由页 有自己的展示区 {props.children} 不然会在全局路由展示
    
      |-goods/category.tsx   // /goods/category 路由
      |-goods/[uid]/comment.tsx // /goods/23/comment 路由
    
    
      |-404.js 生产模式下的404页,//目前有问题 https://github.com/umijs/umi/issues/4437
      //document.ejs 浏览器模板页 没有umi自动生成,有取当前模板结构 ***
    |- layouts
    	|-index.tsx //全局路由。返回一个 React 组件,并通过 props.children 渲染子组件
    

    不同的全局 layout

    你可能需要针对不同路由输出不同的全局 layout,但你仍可以在 src/layouts/index.tsx 中对 location.path 做区分,渲染不同的 layout 。

    比如想要针对 `/user输出简单布局,

    //layouts/user.jsx
    import React from 'react';
    
    export default (props) => {
      return (
        <div>
          <h1>user layouts</h1>
          {props.children}
        </div>
      );
    }
    
    //layouts/index.jsx
    
    import user from './user'
    export default function(props) {
      if (props.location.pathname === '/user') {
        return <User>{ props.children }</User>
      }
    
      return (
        <>
        	...默认全局路由
          { props.children }
        </>
      );
    }
    

    扩展路由属性

    支持在代码层通过导出静态属性的方式扩展路由。

    import React from 'react';
    import './user.css';
    
    function User(){
      return (
        <div>
          <h1 className={'box'}>Page user</h1>
        </div>
      );
    }
    
    User.title = 'user Page';//修改路由页面标题
    
    export default User;
    
    

    其中的 title 会附加到路由配置中。

    路由跳转

    声明式
    import {NavLink} from 'umi'
    商品 002
    商品 002
    
    编程式
    import {history} from 'umi';
    					
    history.push('/login')
    
    history.push('/goods/1?a=1&b=2');
    
    history.push({
      pathname:'/goods/1',
      query:{a:11,b:22},
      search:'a=111&b=222' //如果传递了search 会覆盖query,query只是对search的封装引用了search的值
    })
    
    props.history.push({
      pathname:'/goods/1',
      query:{a:111,b:222},
    })
    

    传接参

    props.match.params
    props.location.query 返回对象
    
    
    import {useLocation,useParams} from 'umi'
    let locationHooks = useLocation();
    let params = useParams();
    
    {locationHooks.pathname}|{params.uid}
    

    授权路由

    前置
    //app.ts  全局
    import { history } from 'umi';
    
    export function render(oldRender) {
      fetch('/api/auth').then(auth => {
        if (auth.isLogin) { oldRender() }
        else { history.push('/login'); }
      });
    }
    
    //路由级别  路由独享
    //umirc
    routes: [
      { path: '/user', component: 'user',
        wrappers: [
          '@/wrappers/auth',
        ],
      },
      { path: '/login', component: 'login' },
    ]
    
    //auth
    export default (props) => {
      const { isLogin } = useAuth();
      if (isLogin) {
        return 
    { props.children }
    ; } else { redirectTo('/login'); } }
    后置
    //同react
     {
        return window.confirm(`确认要去向 ${location.pathname}?`);
      }}
    />
    

    异步路由

    组件体积太大,不适合直接计入 bundle 中,以免影响首屏加载速度

    //启用按需加载  p__index__index.chunk.css	p__index__index.js
    // umirc
    export default {
      dynamicImport: {},
    }
    

    配置型路由

    umirc 中通过 routes 进行配置,格式为路由信息的数组

    routes: [
      { path: '/login', component: 'login' },// 不写路径从 src/pages找组件
      { path: '/reg', component: 'reg' },
    	
      //不使用全局layout的配置可以写在 / 的上面
      
      {
        path: '/',
        component: '@/layouts/index',
        routes: [//通常在需要为多个路径增加 layout 组件时使用
          { path: '/index', component: 'index' },
          // { exact: true, path: '/goods', component: '@/pages/goods' },
          { path: '/goods', component: '@/pages/goods/index' },
          {
            path: '/goods/:uid',
            component: '@/layouts/goods-detail',//为uid层级页面指定layout
            routes: [
              { path: '/goods/:uid', component: '@/pages/goods/[uid]' },
              { path: '/goods/:uid/comment', component: '@/pages/goods/[uid]/comment' },
              { component: '@/pages/404' }//子集的404,每一级都可以设定404
            ],
          },
          {
            path: '/user',
            component: '@/layouts/user',
            routes: [
              { path: '/user', component: '@/pages/user/index' },
              { path: '/user/:id', component: '@/pages/user/[id]' },
              { component: '@/pages/404' },
            ],
          },
          { path:'/', redirect: '/index' }, //跳转
          { component: '@/pages/404' },
        ],
      }
    
    ]
    

    数据交互

    mock

    Umi 约定 /mock 文件夹下所有文件为 mock 文件无需代理,其他服务器数据需要代理

    import mockjs from 'mockjs'
    export default {
      // 支持值为 Object 和 Array
      
      'GET /umi/goods': [{id:1,name:'韭菜'},{id:2,name:'西红柿'}],
    
      // 支持自定义函数,API 参考 express@4
      'POST /umi/login': (req, res) => {
        // 添加跨域请求头
        // res.setHeader('Access-Control-Allow-Origin', '*');
        console.log('req',req.body);//完成业务
        res.send({
          err: 404,
          msg:'登录失败1'
        });
      },
    
      //引入mockjs
    
      'GET /umi/goods/home': mockjs.mock({
        'list|100': [{ name: '@city', 'value|1-100': 50, 'type|0-2': 1 }],
      }),
    
    }
    
    //模拟延时
    import { delay } from 'roadhog-api-doc'
    export default delay({同上},1000)
    

    貌似不支持resFulApi 风格请求,jsonserver支持,值得考虑

    代理

    //umirc 
    proxy: {
      '/api': {
        target: 'http://localhost:9001', //node服务
        "changeOrigin": true,
        // pathRewrite: {'^/api' : ''}
      },
      '/mock': {
        target: 'http://localhost:3333', //自建的jsonserver服务
        "changeOrigin": true,
        // pathRewrite: {'^/mock' : ''},
        // secure: false //接受https的代理
      }
    }
    

    axios

    //  plugins/axios.js 
    import axios from 'axios';
    
    //添加一个请求的拦截
    axios.interceptors.request.use(function (config) {
    
      config.headers={
        'token':'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFsZXgiLCJfaWQiOiI1ZThhMGQ2MzczNDg2MDIzYTRmZDY4ZGYiLCJpYXQiOjE1ODkwMDIzMDUsImV4cCI6MTU4OTA4ODcwNX0.sStWoKBk2mYwa_1-AJOQobL7LBR82DnOseCeTds5ECs'
      };
    
      console.log('axios拦截器');
    
      return config;
    }, function (error) {
      return Promise.reject(error);
    });
    
    // 添加一个响应的拦截
    axios.interceptors.response.use(function (response) {
      return response;
    }, function (error) {
      return Promise.reject(error);
    });
    
    
    // React.axios = axios;//实例属性 无效
    // window.axios = axios; //全局API 无效
    
    export default axios;//需要引入plugins下的axios 才有拦截
    
    

    插件

    umi3提供的插件(内置),基本上无需安装,无需配置,直接使用即可

    scss

    umi 默认支持 less,要使用scss需要添加插件@umijs/plugin-sass,默认已安装,支持 Dart Sass 切换到 Node Sass,需安装 node-sass 依赖

    yarn add @umijs/plugin-sass -D //无需配置就可以支持dart scss
    yarn add node-sass -D // 使用node-sass时 需要的依赖
    
    //使用node-sass时的配置 umirc
    sass: {
      implementation: require('node-sass'),
    }
    

    antd

    内置插件,默认开启,直接使用,对齐antd使用

    import { Button,message } from 'antd';
    <Button 
    	type="primary" 
    	onClick={()=>message.info('message')}
    >Primary</Button>
    

    request

    介绍

    网络请求库,基于 fetch 封装, 兼具 fetch 与 axios 的特点, 旨在为开发者提供一个统一的api调用方式, 简化使用

    特性 request fetch axios
    实现 浏览器原生支持 浏览器原生支持 XMLHttpRequest
    大小 9k 4k (polyfill) 14k
    query 简化
    post 简化
    超时
    缓存
    错误检查
    错误处理
    拦截器
    前缀
    后缀
    处理 gbk
    中间件
    取消请求
    基本用法

    request内置插件,默认开启,直接使用,使用对齐 umi-request 和 @umijs/hooks 的 useRequest

    import {request} from 'umi'
    // useRequest 接收了一个异步函数 getUsername ,在组件初次加载时, 自动触发该函数执行。同时 useRequest 会自动管理异步请求的 loading , data , error 等状态
    //request 对齐 axios
    
    export default (props) => {
      //data ~~ axios的res.data.data
      //如果数据里面没有data 返回undefined
      //通过配置 umirc request.dataField 可以指定
      console.log('data',data)
    
      useEffect(()=>{
    		//request ~~ axios
        request('/api/goods/home',{params:{_limit:1}}).then(
        	//res ~~ axios的res.data
        ).catch()
    
      },[]);
    
      return (
        
    ); }
    拦截器
    //app.ts
    export const request = {
      // timeout: 1000,
      // errorConfig: {},
      // middlewares: [],
      requestInterceptors: [
        (url, options)=>{// 请求地址 配置项
          options.headers={token:''}
          return {url,options}
        }
      ],
      responseInterceptors: [
        (response, options) => {//响应体 请求时的配置项
          console.log(response,options)
          return response;
        }
      ],
    };
    
    useRequest钩子
    介绍

    文档

    一个强大的管理异步数据请求的 Hook.

    核心特性

    • 自动请求/手动请求
    • SWR(stale-while-revalidate)
    • 缓存/预加载
    • 屏幕聚焦重新请求
    • 轮询
    • 防抖
    • 节流
    • 并行请求
    • loading delay
    • 分页
    • 加载更多,数据恢复 + 滚动位置恢复
    • 错误重试
    • 请求超时管理
    • suspense
    用法

    在组件初次加载时, 自动触发该函数执行。同时 useRequest 会自动管理异步请求的 loading , data , error 等状态。

    import {useRequest} from 'umi'
    
    export default function  RequestHooks(){
    
      // 用法 1
      const { data, error, loading } = useRequest('/mock/home');
    
    	// 用法 2
      const { data, error, loading } = useRequest({
        url: '/mock/home',
        params:{_limit:1}
      });
    
    	// 用法 3
    	const { data, error, loading } = useRequest((id)=> `/api/home/${id}`); //?
    
    	// 用法 4
      const { data, loading, run } = useRequest((_limit) => ({
        url: '/mock/home',
        params: { _limit }
      }), {
        manual: true,//手动通过运行run触发
      });
      
      // 轮询
      const { data, loading, run } = useRequest((_limit) => ({
        url: '/mock/home',
        params: { _limit }
      }), {
        manual: true,//手动通过运行run触发
        pollingInterval:1000,//轮询 一秒读一次
        pollingWhenHidden:false,//屏幕不可见时,暂停轮询
      });
      
      if (error) {
        return 
    failed to load
    } if (loading) { return
    loading...
    } return (
    {JSON.stringify(data)}
    ); }
    并行请求

    通过 options.fetchKey ,可以将请求进行分类,每一类的请求都有独立的状态

    import { useRequest } from 'umi';
    import { Button } from 'antd';
    
    export default () => {
      const { run, fetches } = useRequest((userId)=>({
        url: '/mock/home',
        params:{_limit:1,_page:userId-0}
      }), {
        manual: true,
        fetchKey: id => id,
        onSuccess:(res,params)=>{
          console.log(res, params)
        }
      });
    
      const users = [{ id: '1', username: 'A' }, { id: '2', username: 'B' }, { id: '3', username: 'C' }];
    
      return (
        

    并行请求:单击所有按钮,每个请求都有自己的状态

      {users.map((user => (
    • )))}
    ); };
    防抖-解流-缓存
    import { useRequest, request } from 'umi';
    import { Input  } from 'antd';
    import React from 'react';
    
    let {Search}=Input;
    
    function getHome(search) {
      console.log(1,search)
      return request('/mock/home',{params:{_page:search-0, _limit:1}})
    }
    
    export default () => {
      const { data, loading, run, cancel } = useRequest(getHome, {
        // debounceInterval: 500, //频繁调用 run 以防抖策略进行请求
        // throttleInterval: 500,//频繁触发 run ,则会以节流策略进行请求
        // cacheKey:'homepage', //缓存 回退路由,在进入数据data还在
        manual: true
      });
    
      return (
        

    防抖

    run(e.target.value)} onBlur={cancel} style={{ width: 300 }} loading={loading} />
    {data && JSON.stringify(data)}
    ); };
    激活聚焦重求-loading防闪烁
    import { useRequest } from 'umi';
    import { Spin } from 'antd';
    import React from 'react';
    
    export default () => {
      const { data, loading } = useRequest({
        url:'/mock/home',
        params:{_limit:1,_page:1}
      }, {
        refreshOnWindowFocus: true,//浏览器窗口 refocus 和 revisible 时,会重新发起请求
        focusTimespan: 1000,//请求间隔,默认为 5000ms 。
        // loadingDelay:1500 // loading防闪烁 可以延迟 loading 变成 true 的时间,有效防止闪烁
      })
    
      return (
        

    屏幕聚焦重新请求

    {data && JSON.stringify(data)}
    ) }
    状态变化重请求
    import { useRequest } from '@umijs/hooks';
    import { Spin, Select } from 'antd';
    import React, { useState } from 'react';
    
    export default () => {
      const [pageNum, setPageNum] = useState('1');
    
      const { data, loading } = useRequest(() => ({
        url:'/mock/home',
        params:{_limit:1,_page:pageNum}
      }), {
        refreshDeps: [pageNum]//pageNum变化时,会使用之前的 params 重新执行
      });
    
      return (
        
    home: {data && JSON.stringify(data)}
    ); };

    app.ts 配置

    运行时配置,跑在浏览器端, 如:在出错时显示个 message 提示用户,在加载和路由切换时显示个 loading,页面载入完成时请求后端,根据响应动态修改路由,引入一些插件配置模块,在运行时带入这些配置

    配置思想

    对外导出一堆umi内部可识别的函数 来完成配置

    //修改路由
    export function patchRoutes({ routes }) {
      //比如在最前面添加一个 /foo 路由
      routes.unshift({
        path: '/foo',
        exact: true,
        component: require('@/extraRoutes/foo').default,
      });
    }
    
    //权限校验
    import { history } from 'umi';
    
    export function render(oldRender) {
      fetch('/api/auth').then(auth => {
        if (auth.isLogin) { oldRender() }
        else { history.push('/login'); }
      });
    }
    
    //动态更新路由
    let extraRoutes;
    export function patchRoutes({ routes }) {
      merge(routes, extraRoutes);
    }
    export function render() {
      //请求服务端根据响应
      fetch('/api/routes').then((res) => { extraRoutes = res.routes })
    }
    
    export function onRouteChange({matchedRoutes, location, routes, action }) {
      //初始加载和路由切换时的逻辑,用于路由监听, action 是路由切换方式如:push
      //用于做埋点统计
      //动态设置标题
      document.title = matchedRoutes[matchedRoutes.length - 1].route.title || ''
    }
    
    export function rootContainer(container,args) {
      //封装 root container  外层有个 Provider 要包裹 的场景 必须要有返回值,无需是可以不写这个函数
      // const DvaContainer = require('@tmp/DvaContainer').default;
      return React.createElement(DvaContainer, null, container);
      
      //args 包含:
    
        //routes,全量路由配置
        //plugin,运行时插件机制
        //history,history 实例
    }
    
    export function modifyRouteProps(props, { route }) { // ***
      	//修改传给路由组件的 props , 所有组件都中
        //props,Object,原始 props
        //route,Object,当前路由配置
        return { ...props, 混入后的key: 值 };
    }
    

    umirc 配置

    配置编译环境 umirc 无需重启, 推荐在 .umirc.ts 中写配置。如果配置比较复杂需要拆分,可以放到 config/config.ts 中,并把配置的一部分拆出去,比如路由。

    两者二选一,.umirc.ts 优先级更高。 文档

    import { defineConfig } from 'umi';
    
    export default defineConfig({
    	
      //设置 node_modules 目录下依赖文件的编译方式
      nodeModulesTransform: {
        type: 'none',
      },
    
    
      // 配置型路由 权重高于约定式 且不可合并
      // routes: [
      //   { component: '@/pages/404' },
      // ],
    
      history: { type: 'hash' }, //哈希路由模式,解决强刷,或者通过后端解决
    
      //关闭mock
      // mock: false,
    
      //多个代理 mock数据无需代理
      proxy: {
        '/api': {
          target: 'http://localhost:9001',
          "changeOrigin": true,
          // pathRewrite: {'^/api' : ''}
        },
        '/mock': {
          target: 'http://localhost:333',
          "changeOrigin": true,
          // pathRewrite: {'^/mock' : ''},
          // secure: false //接受https的代理
        }
      },
    
      //按需加载功能默认是关闭的
      dynamicImport: {
        loading: '@/loading', //定义按需加载时的loading组件
      },
    
      title: 'hi',//配置应用统一标题
    
      mountElementId:'app',//指定 react app 渲染到的 HTML 元素 id。
    
      devServer:{
        port:8082
      },
    
      favicon: '/favicon.ico',//使用本地的图片,图片请放到 public 目录
    
      //配置  里的额外脚本,数组项为字符串或对象
      headScripts: [
        `alert(1);`,
        `http://code.jquery.com/jquery-2.1.1.min.js`,
      ],
    
      //配置  额外 link 和style
      styles: [
        `body { color: red; }`,
        `https://a.com/b.css`,
      ],
    
      
    });
    
    

    SSR


    参考

    dva

    dva = React-Router + Redux + Redux-saga 项目

    核心

    • View:React 组件构成的视图层

    • Action:一个对象,描述事件

    • connect 方法:一个函数,绑定 State 到 View

    • dispatch 方法:一个函数,发送 Action 到 State

    • model 数据管理模块

      • State:一个对象,保存整个应用状态
      • reducers: 一个对象 同步业务
      • effects: 一个对象 处理异步
      • subscriptions: 订阅数据源

      img

    umi2下使用

    umirc配置

    antd: true, 开启antd
    dva: true,  开启dva数据流
    routes: {//路由
      exclude: [//排除
        /models\//,
        /services\//,
        /model\.(t|j)sx?$/,
        /service\.(t|j)sx?$/,
        /components\//,
      ],
    },
    

    app配置

    export const dva = {
      config: { 
        onError(err) {//监听错误
          err.preventDefault();
          console.error('dva config',err.message);
        },
    
        //初始数据 不给就取根models的state,给了就不取
        initialState: {
          namespace: {
            key:value
          },
        },
      },
      plugins: [
        // require('dva-logger')(),
      ],
    }
    

    model

    // src/models 全局
    // src/pages/models 页面
    
    export default {
      namespace: 'global',//所有models里面的namespace不能重名
      state: { //存放数据
        stateName: stateValue,
      },
      reducers: { //处理同步 左key 等于dispatch({type:key
        reducersKey(state,{type,payload}) {
          return {
            ...state,
            stateName: newValue,
          };
        }
      },
      effects: {	//处理异步 左key 等于dispatch({type:key
        *login(action, { call, put, select }) {
    
          //	call:执行异步函数  如: const result = yield call(fetch, '/todos');
          //	put:发出一个 Action,类似于 dispatch
          //	select: 从state里获取数据 如: const todos = yield select(state => state.todos);
    
          const res = yield call(fetch,'/mock/home')
          const data = yield res.json();
    
          yield put({
            type: 'reducerKey'|'effectsKey',
          });
    
        },
      },
    
      subscriptions: {
        //场景: 时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化
        //订阅一个数据源 根据条件 dispatch 需要的 action
        //subsription中的方法名是随意定的,每次变化都会一次去调用里面的所有方法
    
        随意的key({ dispatch, history }) {
    
          //路由监听
          history.listen(({ pathname, query }) => {});
    
          //需要导入import key from 'keymaster' 监听键盘
          key('⌘+i, ctrl+i', () => { dispatch({type:reducersKey|effectsKey}) });
    
          //窗口变化
          window.onresize|onscroll = function(){
            console.log('onresize')
          }
    
        }
      }
      
    }
    

    页面

    layouts、组件接入 dva

    import {connect} from 'dva'
    function 组件(props){
      props.propname
      props.dispatch({type,payload})
        //type:'namespace/reducersKey|effectsKey'
        //type:'namespace/effectsKey'
    }
    function mapStateToProps(state) {
      return {
        propname: state.namespace.stateKey,
        propname: state.namespace
      };
    }
    export default connect(mapStateToProps)(组件);
    
    //layouts
    import withRouter from 'umi/withRouter';
    import {connect} from 'dva'
    export default withRouter(connect(mapStateToProps)(组件));
    

    umi3下使用

    umi3以插件的形式整合 dva 数据流,配置默认开启

    文档

    model

    // src/models/modelname.js 全局
    
    
    import { history } from 'umi';
    import key from 'keymaster';
    
    export default {
      namespace: 'global',//所有models里面的namespace不能重名
      state: {
        title:'UMI+DVA',
        text: '我是全局text',
        login: false,
        a:'全局models aaaa',
      },
      reducers: {//处理同步 左key 等于dispatch({type:key
        setText(state) {
          return {
            ...state,
            text: '全局设置 后的text'+Math.random().toFixed(2),
          };
        },
        setTitle(state,action) {
          return {
            ...state,
            text: `全局设置 后的title${action.payload.a}/${action.payload.b}`,
          };
        },
        signin:(state)=>({
          ...state,
          login: true,
        }),
      },
      effects: {
        //处理异步 左key 等于dispatch({type:key
        //call:执行异步函数
          // const result = yield call(fetch, '/todos');
        //put:发出一个 Action,类似于 dispatch
        //select: 从state里获取数据
          //const todos = yield select(state => state.todos);
        *login(action, { call, put, select }) {
          const res = yield call(fetch,'/umi/goods/home')
          const data = yield res.json();
          console.log('*login',data);
    
          yield put({
            type: 'signin',
          });
    
          yield put(history.push('/'));
          // yield put(routerRedux.push('/'));
        },
    
    
        *throwError(action, effects) {
          console.log(effects);
          throw new Error('全局effects 抛出的 error');
        },
      },
      subscriptions: {
        //场景: 时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化
        //订阅一个数据源 根据条件 dispatch 需要的 action
        //subsription中的方法名是随意定的,每次变化都会一次去调用里面的所有方法
        listenRoute({ dispatch, history }) {
          history.listen(({ pathname, query }) => {
            console.log('global subscriptions',pathname,query);//根据不同pathname加载不同数据发actions给reducers组件绑定state就好
          });
        },
        listenKeyboard({dispatch}) {//监听键盘
          key('⌘+i, ctrl+i', () => { dispatch({type:'setText'}) });
        },
        listenResize({dispatch}) {//监听窗口变化
          window.onresize = function(){
            console.log('onresize')
          }
        },
        listenScroll({dispatch,history,done}){
          window.οnscrοll=function () {
            console.log('onscroll')
          }
    
    
        }
      },
    };
    
    
    // src/pages/pagename/model.js 页面
    //model下面如果 没有多个state key 可以不用出现models目录
    export default {
      namespace: 'count',
      state: 0,//count:0
      reducers: {
        increase(state) {
          return state + 1;
        },
        decrease(state) {
          return state - 1;
        },
      }
    };
    
    // src/pages/pagename/models/modelname.js 页面
    export default {
      namespace: 'a',  //组件 通过state.a 得到 'goods page data a'
      state: 'goods page data a',
      reducers: {},
    };
    

    组件

    layouts、组件接入 dva

    import {connect} from 'umi'
    function 组件(props){
      props.propname
      props.dispatch({type,payload})
        //type:'namespace/reducersKey|effectsKey'
        //type:'namespace/effectsKey'
    }
    function mapStateToProps(state) {
      return {
        propname: state.namespace.stateKey,
        propname: state.namespace
      };
    }
    export default connect(mapStateToProps)(组件);
    
    //layouts
    import {withRouter, connect} from 'umi'
    export default withRouter(connect(mapStateToProps)(组件));
    

    @umijs/hooks

    文档

    useDrop & useDrag

    一对帮助你处理在拖拽中进行数据转移的 hooks

    useDrop 可以单独使用来接收文件、文字和网址的拖拽。

    useDrag 允许一个 dom 节点被拖拽,需要配合 useDrop 使用。

    向节点内触发粘贴时也会被视为拖拽的内容

    使用

    import React from 'react';
    import { useDrop, useDrag } from '@umijs/hooks';
    
    export default () => {
      const getDragProps = useDrag();
      //props  需要透传给接受拖拽区域 dom 节点的 props
      //isHovering 是否是拖拽中,且光标处于释放区域内
      const [props, { isHovering }] = useDrop({
        onText: (text, e) => {
          console.log(text, e);
        },
        onFiles: (files, e) => {
          console.log(e, files);
        },
        onUri: (uri, e) => {
          console.log(uri, e);
        },
        onDom: (content, e) => {
          console.log(content, e);
        },
      });
    
      return (
        
    useDrop 可以单独使用来接收文件、文字和网址的释放
    useDrag 允许一个 dom 节点被拖拽,需要配合 useDrop 使用
    向节点内触发粘贴时也会被视为拖拽的内容
    {isHovering ? '撒手' : '拖到这'}
    box
    ); };

    useDrag Result

    参数 说明 类型
    getDragProps 一个接收拖拽的值,并返回需要透传给被拖拽节点 props 的方法 (content: any) => props

    useDrop Result

    参数 说明 类型
    props 需要透传给接受拖拽区域 dom 节点的 props -
    isHovering 是否是拖拽中,且光标处于释放区域内 boolean

    useDrop Params

    参数 说明 类型 默认值
    onText 拖拽文字的回调 (text: string, e: Event) => void -
    onFiles 拖拽文件的回调 (files: File[], e: Event) => void -
    onUri 拖拽链接的回调 (text: string, e: Event) => void -
    onDom 拖拽自定义 dom 节点的回调 (content: any, e: Event) => void -

    useVirtualList

    解决展示海量数据渲染时首屏渲染缓慢和滚动卡顿问题

    使用

    import React from 'react';
    import { useVirtualList } from '@umijs/hooks';
    
    export default () => {
      //list 当前需要展示的列表内容  {data: T, index: number}[]
      // containerProps  滚动容器的 props  {}
      // wrapperProps  children 外层包裹器 props {}
      const { list, containerProps, wrapperProps } = useVirtualList(Array.from(Array(99999).keys()), {
        overscan: 30,//视区上、下额外展示的 dom 节点数量
        itemHeight: 60,//行高度,静态高度可以直接写入像素值,动态高度可传入函数
      });
      return (
        <>
          
    {list.map((ele, index) => (
    Row: {ele.data}
    ))}
    ); };

    API

    const result:Result = useVirtualList(originalList: any[], Options);
    

    Result

    参数 说明 类型
    list 当前需要展示的列表内容 {data: T, index: number}[]
    containerProps 滚动容器的 props {}
    wrapperProps children 外层包裹器 props {}
    scrollTo 快速滚动到指定 index (index: number) => void

    Params

    参数 说明 类型 默认值
    originalList 包含大量数据的列表 T[] []
    options 可选配置项,见 Options - -

    Options

    参数 说明 类型 默认值
    itemHeight 行高度,静态高度可以直接写入像素值,动态高度可传入函数 number | ((index: number) => number) -
    overscan 视区上、下额外展示的 dom 节点数量 number 10

    useEventTarget

    常见表单控件(通过 e.target.value获取表单值) 的 onChange 跟 value 逻辑封装,支持 自定义值转换 跟 重置 功能

    使用

    import React  from 'react';
    import { Input, Button } from 'antd';
    import { useEventTarget } from '@umijs/hooks'
    
    export default () => {
      //value  表单控件的值 T
      // onChange  表单控件值发生变化时候的回调 (e: { target: { value: T }}) => void
      // reset 重置函数 () => void
      const [valueProps, reset] = useEventTarget('初始值');
    
      return (<>
          
          
        
      );
    };
    

    API

    const [ { value, onChange }, reset ] = useEventTarget<T, U>(initialValue?: T, transformer?: (value: U) => T );
    

    Result

    参数 说明 类型
    value 表单控件的值 T
    onChange 表单控件值发生变化时候的回调 (e: { target: { value: T }}) => void
    reset 重置函数 () => void

    Params

    参数 说明 类型 默认值
    initialValue? 可选项, 初始值 T -
    transformer? 可选项,可自定义回调值的转化 (value: U) => T -

    umi+dva项目

    开发

    部署

    项目1

    项目2

    umi+dva+ts

    项目3

    项目4

    React-Native

    React-Native介绍

    React Native (简称RN)是Facebook于2015年4月开源的跨平台移动应用开发框架,是Facebook早先开源的UI框架 React 在原生移动应用平台的衍生产物,目前支持iOS和安卓两大平台。RN使用Javascript语言,类似于HTML的JSX,以及CSS来开发移动应用,因此熟悉Web前端开发的技术人员只需很少的学习就可以进入移动应用开发领域。
      React Native使你能够在Javascript和React的基础上获得完全一致的开发体验,构建世界一流的原生APP。React Native着力于提高多平台开发的开发效率 —— 仅需学习一次,编写任何平台。(Learn once, write anywhere),官网

    Native App

    	即原生开发模式,开发出来的是原生程序,不同平台上,Android和iOS的开发方法不同,开发出来的是一个独立的APP,能发布应用商店,有如下优点和缺点。
    
    

    优点:

    • 直接依托于操作系统,交互性最强,性能最好
    • 功能最为强大,特别是在与系统交互中,几乎所有功能都能实现

    缺点:

    • 开发成本高,无法跨平台
    • 升级困难
    • 维护成本高

    Web App

    	即移动端的网站,将页面部署在服务器上,然后用户使用各大浏览器访问,不是独立APP,无法安装和发布Web网站一般分两种,MPA(Multi-page Application)和SPA(Single-page Application)。而Web App一般泛指后面的SPA形式开发出的网站(因为可以模仿一些APP的特性),有如下优点和缺点。
    
    

    优点:

    • 开发成本低,可以跨平台,调试方便
    • 版本升级容易
    • 维护成本低
    • 无需安装 App,不占用手机内存(通过浏览器即可访问)

    缺点:

    • 性能低,用户体验差
    • 依赖于网络,页面访问速度慢,耗费流量
    • 功能受限,大量功能无法实现(无法调用原生 API)
    • 临时性入口,用户留存率低

    Hybrid App

    即混合开发,也就是半原生半Web的开发模式,有跨平台效果,实质最终发布的仍然是独立的原生APP(各种的平台有各种的SDK),这是一种 Native App 和 Web App 折中的方案,保留了 Native App 和 Web App 的优点。
    优点:

    • 开发成本较低,可以跨平台,调试方便
    • 维护成本低,功能可复用
    • 更新较为自由(只下载资源不更新 apk )
    • 学习成本较低(前端开发人员不用学习底层 api)
    • 功能更加完善,性能和体验要比起web app 好

    缺点:

    • 相比原生,性能仍然有较大损耗
    • 不适用于交互性较强的app(主要适用于新闻阅读类与信息展示类的 APP)

    React Native App

    	Facebook发起的开源的一套新的APP开发方案,Facebook在当初深入研究Hybrid开发后,觉得这种模式有先天的缺陷,所以果断放弃,转而自行研究,后来推出了自己的“React Native”方案,不同于H5,也不同于原生,更像是用JS写出原生应用,有如下优点和缺点
    
    

    优点:

    • 开发成本在 Hybrid 和 Native 开发之间 ,大部分代码还是可复用的,
    • 性能体验高于Hybrid,性能相比原生差别不大
    • 技术日益成熟,发展迅猛

    缺点:

    • 门槛相对 Web App 与 Hybrid App 来说相对高一点(也需要了解 Native 层)

    开发模式的对比

    img

    环境搭建

    使用expo-cli开发react应用程序需要两种工具:本地开发工具和用于打开应用程序的移动客户端,需要在计算机上安装Node.js(版本10或更新版本)

    安装Expo CLI 到pc开发机器
    npm install -g expo-cli
    
    
    手机上安装 expo client

    从Play商店下载Android版或从App Store 下载iOS版

    • 真机测试 更真实
    • 打开手机expo client->profile->options->sigin in 注册一个账号 并 登陆
    • 安卓真机需要 打开usb调试

    pc上装模拟器

    • pc机模拟手机来测试 速度快
    • for windows: 夜游,逍遥… 推荐: mumu 网易模拟器,百度一下
      • 在mumu模拟器上安装 apk -> expo client安装到模拟器 -> 允许expo在其他应用上层显示->登陆expo账号
    • for ios: Xcode, 目前只有安卓模拟器,没有好用的苹果模拟器

    创建react-native项目

    expo init 目录
    
    
    选择模板: (Use arrow keys)
      ----- Managed workflow -----
    > blank         空模板 
      tabs          带路由
    
    
    cd 目录
    npm start | yarn start  看你init时用的是什么工具安装,这里就用什么工具
    
    
    ? 回车   查看expo帮助
    s 回车   登录expo账号   输入在手机expo注册的账号, 登录只需要做一次
    shift + r 重启
    npm start | yarn start 
    
    

    注意

    • 保持 手机上expo的账号 和项目创建时 s 的账号一致
    • 保持pc和手机在同一网段,后期热刷新
    • 卡顿时,摇手机 refresh -> pc 机 npm | yarn start + 手机expo client重开

    打包apk

    npm install -g expo-cli  已安装侧跳过
    
    

    配置app.json

    {
      "expo": {
        "name": "应用名称",
        "slug": "expo-test-win10", //上传到expo时的目录名
        "icon": "./assets/icon.png", //桌面图标
        "splash": {
          "image": "./assets/splash.png",//欢迎图片
          "resizeMode": "contain",
          "backgroundColor": "#ffffff"
        },
        "updates": {
          "fallbackToCacheTimeout": 0
        },
        "assetBundlePatterns": [
          "**/*"
        ],
        "ios": {
          "supportsTablet": true
        },
        "android": {
          "package": "top.uncle9.expo"  //添加安卓的package属性,域名倒放,有无域名无所谓	
        }
      }
    }
    
    
    expo build:android  打包到安装apk,ios需要开发id
    
    
    1) Let Expo handle the process!		√  让Expo为您生成密钥库
    2) I want to upload my own keystore! 上传自己的
    
    

    Published完成后有个在线地址,或者登陆expo账号去下载apk,之后安卓到模拟器,或真机官网参考

    样式

    	所有的核心组件都接受名为`style`的属性,使用了驼峰命名法,例如将`background-color`改为`backgroundColor`,可以传入一个数组——在数组中位置居后的样式对象比居前的优先级更高,这样你可以间接实现样式的继承,尺寸都是无单位的,表示的是与设备像素密度无关的逻辑像素点
    
    

    用法1: xx

    用法2: xx

    用法3: xx

    const styles = StyleSheet.create({
      red: {
        color: 'red',
      },
    });
    
    

    布局

    React Native 中使用 flexbox 规则来指定某个组件的子元素的布局,flexDirection的默认值是column而不是row,而flex也只能指定一个数字值。布局图解

    相对定位

    React Native 中,position 默认值为 relative,即相对布局。

    • 如果父组件没有设置 flex 值,则子组件不论是否设置 flex ,子组件都不会显示在界面上

    如下示例代码中,子组件会等比例的占满屏幕

    return (
          
            
            
            
          
        );
    
    
    • 如果父组件设置了 flex 值,子组件设置了 flex 值的同时,也设置了高度值,则高度无效

    如下示例代码中,子组件会等比例的占满屏幕,设置高度并不影响所占的比例

    return (
          
            
            
            
          
        );
    
    
    • 如果父组件设置了 flex 值,子组件没有设置 flex 值,只设置了高度值,则高度有效

    如下示例代码中,子组件在界面所占的比例受高度控制,最后一个子组件则自动占满剩余空间

    return (
          
            
            
            
          
        );
    
    

    绝对布局

    样式设置 position: ‘absolute’

    • 绝对布局情况下,设置 flex 不能达到页面整屏占满的效果,需要同时设置组件的宽高,否则父组件与子组件都将无法显示

    正确设置父组件的样式方式

    return (
          
            
            
            
          
        );
    
    
    • 只设置父组件的宽,设置子组件的高度有效,子组件的flex无效

    如下示例代码中,只有第一个设置了高度的view会显示在界面上,而设置了flex的view不会显示

    return (
          
            
            
            
          
        );
    
    
    • 只设置父组件的高,则设置子组件的高度和flex均无效,父组件与子组件都无法显示

    如下示例代码中,父组件与子组件都不会显示

    return (
          
            
            
            
          
        );
    
    
    • 同时设置了父组件的宽和高的前提下,再设置子组件的高度和flex均有效

    如下示例代码中,分别设置了高度和flex的view均会显示在界面上

    return (
          
            
            
            
          
        );
    
    
    • 父组件与子组件同时绝对布局的情况下,且设置绝对布局的子组件写在最后一个时,保持原则一样,则父组件与子组件可正常显示

    如下示例代码中,子组件均能正常显示,且设置了flex的view会将剩余的屏幕占满显示,不会被绝对布局的同级子view阻挡

    return (
          
            
            
            
          
    
    
    • 父组件与子组件同时绝对布局的情况下,且设置绝对布局的子组件没有写在最后一个时,保持原则一样,则绝对布局的子组件不会显示在父组件中,它会被前一个子组件覆盖

    如下示例代码中,除相对布局的子组件能正常显示外,绝对布局的子组件会被同级的子view覆盖

    return (
          
            
            
            
          
        );
    
    

    另外,可以给子组件设置不同的flex值,如flex:2, flex: 0.5,则子组件在界面上所占的比例会随之变化。

    文本输入

    注意 react 中的 onChange 对应的是 rn 中的 onChangeText

    触摸事件

    TouchableHighlight按下时变暗

    TouchableOpacity降低按钮的透明度 可选

    滚动视图

    • 放置在ScrollView中的所有组件都会被渲染

    长列表

    • FlatList优先渲染屏幕上可见的元素
    • SectionList 带有分组标签

    网络请求

    • 不存在跨域,安全机制与网页环境有所不同
    • 为了安全默认只能访问https,对http做了限制
    • 建议你增加HTTPS支持,而不是关闭http限制,会把 苹果提供的安全保障也被关闭了
    • 可以使用所有数据交互库,不含jq

    平台判断

    针对不同平台编写不同代码的需求

    • 使用Platform模块.
    • 使用特定平台扩展名.
    • 某些属性可能只在特定平台上有效
    • ActionSheetIOS 与 DatePickerAndroid

    组件与API

    组件规定视图表现,api实现编程式组件操作

    react native 组件

    Picker
    图片
    • 本地图片,可获取实际宽高,require里面必须是字符
    • 网络图片,获取不到宽高,要给设定尺寸
    • 背景图片必须指定宽高样式

    expo组件

    音频 Audio
    视频 Video
    相机 Camera

    路由

    	推荐React Navigation 提供了简单易用的跨平台导航方案,在 iOS 和 Android 上都可以进行翻页式、tab 选项卡式和抽屉式的导航布局
    
    

    安装:

    yarn add react-navigation --save  其他都不用做
    
    
    • navigation prop传递给每个在stack navigator中定义的路由
    • this.props.navigation.navigate(‘Details’) 无栈,添加栈
    • this.props.navigation.goBack() 回退
    • this.props.navigation.push() 强制添加栈
    • this.props.navigation.popToTop() 后退到 第一个栈
    • 跳转到新路由,老路由组件不会卸载,返回时前组件会卸载

    你可能感兴趣的:(react--随笔3)