react数据共享(context/react-redux/typescript版)

396 阅读5分钟

Css模块化

  • 方式一
    • 直接将style.css改名为style.module.css
    • 使用import style from './style.module.css的方式引起
    • 使用<div className={style.xxx}>来使用样式
  • 方式二
    • npm run eject暴露webpack.config.js
    • 搜索cssRegex,在底部添加配置
    • 使用方式与方式一相同(不需要将style.css改为style.module.css)
// 项目/config/webpack.config.js
// 1.在cssRegex配置modules和exclude
// 2.新增一个cssRegex,配置include(重要,不添加会出现全局样式不生效的bug)
// 3.在cssModuleRegex配置localIdentName

{
  test: cssRegex,
  use: getStyleLoaders({
    importLoaders: 1,
    //1. 新增modules配置名字
    modules:{
      localIdentName: "[path]-[name]-[local]-[hash:base64:6]"
    },
    sourceMap: isEnvProduction
      ? shouldUseSourceMap
      : isEnvDevelopment,
  }),
  //2.配置全局样式(该文件夹内不用模块化)
  exclude:[
    path.join(__dirname,"..","node_modules"),
    path.join(__dirname,"..","src/assets/css/common"),
    path.join(__dirname,"..","src/components"),
  ],
  sideEffects: true,
},

//3.添加一个loader配置(不加这段,全局样式会不生效)-------
{
  test:cssRegex,
  use:["style-loader","css-loader"],
  include:[
    path.join(__dirname,"..","node_modules"),
    path.join(__dirname,"..","src/assets/css/common"),
    path.join(__dirname,"..","src/components"),
  ]
},
//---------------------------------------
{
  test: cssModuleRegex,
  use: getStyleLoaders({
    importLoaders: 1,
    sourceMap: isEnvProduction
      ? shouldUseSourceMap
      : isEnvDevelopment,
    modules: {
      // getLocalIdent: getCSSModuleLocalIdent,
      //4.在cssModuleRegex里配置模块化命名
      localIdentName: "[path]-[name]-[local]-[hash:base64:6]"
    },
  }),
},

redux的使用

使用步骤

  • 创建
// 根组件
//1.引入redux和react-redux
import { createStore } from 'redux'
import { Provider } from 'react-redux'
//2. 准备数据state和方法action
function counterReducer (state = { count: 4 }, { type, payload }) {
  switch (type) {
    case 'INC':
      return Object.assign({}, state, payload)
    case 'DEC':
      return Object.assign({}, state, payload)
    default:
      return state

  }
}
//3. 把数据state和方法action存进redux仓库里
let store = createStore(counterReducer)

//4. 注入仓库:使用Provider包裹在根组件外部
ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

  • 使用
import React, { Component } from 'react'
import { connect } from 'react-redux'
class Index extends Component {
    constructor(props) {
        super()
        this.state = {
            num: props.state.count
        }
    }

    increment () {
        /*
            this.props.dispatch({type,payload})
                type:必填,必须叫type,代表action里的某一个方法名
                payload:选填,可随便取名,代表要传递的数据
        */
        this.props.dispatch({ type: 'INC', payload: { count: ++this.state.num } })
    }

    decrement () {
        this.props.dispatch({ type: 'DEC', payload: { count: --this.state.num } })
    }
    render () {
        return (
            <div>
                首页
                <h3>数量:{this.state.num}</h3>
                <button onClick={this.decrement.bind(this)}>-</button>
                <button onClick={this.increment.bind(this)}>+</button>
            </div>
        )
    }
}

export default connect(state => {
    return {
        state: state
    }
})(Index)

Saga的使用

  • 创建
    • redux-saga/effects引入方法
    • 创建一个异步方法function* saga
    • 定义一个方法function* xxx,把异步方法使用takeLatest放入其中
    • 导出定义好的saga
// src/saga/index.js
import { call, put, takeLatest } from 'redux-saga/effects'
import Api from './../apis'

/**
* call(fn,argument)
*/
function* fetchProjectInfo({ payload }) {
    const res = yield call(Api.fetchProjectInfo, payload.workspaceId) // call(fn,arg),接口的参数需要写在后面,和接口方法平级,都是call的参数
    yield put({ type: 'FETCH_PROJECT_INFO', payload: res })
}

function* rootSaga() {
    yield takeLatest('SAGA_PROJECT_INFO', fetchProjectInfo)
}

export default rootSaga
  • 使用
    • 引入createSagaMiddleware
    • 执行const sagaMiddleware = createSagaMiddleware()
    • 引入applyMiddleware
    • 注入store:applyMiddleware(sagaMiddleware)
// src/store/index.js

import { createStore, applyMiddleware } from 'redux'
import reducers from './reducers'

import createSagaMiddleware from 'redux-saga'
import rootSaga from '../saga'

const sagaMiddleware = createSagaMiddleware()

const store = createStore(
    reducers,
    applyMiddleware(sagaMiddleware)
)

sagaMiddleware.run(rootSaga)
export default store

react + typescript

//创建项目
// create-react-app typescript-react-app --typescript

//打开项目
// cd typescript-react-app

//安装依赖,建议使用yarn
//yarn

//运行
//npm run start

数据共享

context

仓库:github.com/santa945/ty…

分支:feature/context

context创建
  • 创建一个context:React.createContext()
  • 添加默认值:defaultValue={key:str|number|boolen|fn|...}
  • 解构出Provider与Consumer
  • 生成一个类组件
  • render函数return时包在最外层
  • Provider的value属性是一个以defaultValue同key的对象:

import React,{Component} from 'react'
/**
 * 1.创建一个context:React.createContext()
 * 2.添加默认值:defaultValue={key:str|number|boolen|fn|...}
 * 3.解构出Provider与Consumer
 * 4.生成一个类组件
 * 5.render函数return时包在最外层
 * 6.Provider的value属性是一个以defaultValue同key的对象:<Provider value={obj}></Provider>
 */

 interface IProps{
    children: ReactNode
 }

 type IState={
     auth: boolean,
     username: string,
 }
const defaultValue = {
    auth:false,
    username:'',
    signin:()=>{}
}

let {Provider,Consumer:AppConsumer} = React.createContext(defaultValue)

class AppProvider extends Component<IProps,IState> {
    constructor(props:IProps){
        super(props)
        this.state={
            auth:false,
            username:''
        }
    }
    signIn = ()=>{
        this.setState({
            auth:true,
            username:'John'
        })
    }
    render(){
        return (
            <Provider value={{
                auth:this.state.auth,
                username:this.state.username,
                signin:()=>this.signIn()
            }}>
                {
                    this.props.children
                }
            </Provider>
        )
    }
}

export {
    AppProvider,//导出包在要用context的组件上
    AppConsumer// 导出包在要用context里的数据的组件上
}

context使用
  • 在要用到context的组件外部包上AppProvider
  • 需要消费context共享的数据的组件包上AppConsumer
//App.tsx
import {AppProvider} from '../src/context/AppContext'

function App() {
  return (
    <AppProvider>
      <div className="App">
        <User></User>
      </div>
    </AppProvider>
  );
}

//users/Index.tsx

import Welcome from './Welcome'
import Login from './Login'
import { AppConsumer} from "./../../context/AppContext"

export default class Index extends Component {
    render() {
        return (
            <AppConsumer>
                {
                    ({auth})=>{
                        return auth ? <Welcome></Welcome> : <Login></Login>
                    }
                }
                
            </AppConsumer>
        )
    }
}

//Login.tsx
import {AppConsumer} from './../../context/AppContext'
export default class Login extends Component {
    render() {
        return (
            <AppConsumer>
                {
                  ({signin})=>{
                      return <button onClick={signin}>login</button>
                  }
                }
            </AppConsumer>
        )
    } 
}


Redux

仓库:github.com/santa945/ty…

分支:feature/redux

//安装依赖
//yarn add redux react-redux

//需要安装react-redux的类型声明文件
// yarn add @types/react-redux
store的创建
  • 在根文件夹创建store/index.ts
  • 创建store,塞入reducers
//store/index.ts
import { createStore} from 'redux'
import reducers from './reducers'
const store = createStore(reducers);

export default store;
reducers的创建

通常情况下不同道模块会有自己的reducers,所以使用模块化处理,在store下创建reducers文件夹,用index.ts合并每一个reducers

  • 创建单个reducers,为一个函数,接收一个state和action
  • 创建合并道reducers/index.ts,引入combineReducers,接收每一个单独道reducers,导出合并的reducers
//store/reducers/index.ts
import { combineReducers} from 'redux'
import todos from './todo'

export default combineReducers({
    todos
})

//store/reducers/todo.ts
const TodoReducers = (state,action)=>{
    //...
}
export default TodoReducers;
types和aciton的定义
  • 单独在store文件夹下建立两个文件夹,将类型放入
  • 单独到reducers使用types和action的类型
// store/types/index.ts
export interface ITodo {
    id: number,
    text: string,
    completed: boolean
}
//------------------------------------------------
//store/action/index.ts
const ADD_TODO = 'ADD_TODO'
const TOGGLE_TODO = 'TOGGLE_TODO'


export interface ITodoAciton {
    type: typeof ADD_TODO | typeof TOGGLE_TODO ,
    id: number,
    text: string
}


//------------------------------------------------

//store/reducers/todo.ts
import {ITodo} from '../types'
import {ITodoAciton} from '../action'

const defaultValue = {
     id:0,
    text:'hello',
    completed:false
}
const todoReducers = (state:Array<ITodo>=[defaultValue],action:ITodoAciton)=>{
    switch (action.type){
        case 'ADD_TODO':
            return [
                ...state,
                {
                    id:action.id,
                    text:action.text,
                    completed:false
                }
            ]
        default:
            return state

    }
}

export default todoReducers
创建connect
//pages/todo/connect.ts
import { connect } from 'react-redux'
import { ITodo } from '../../store/types'
import { Dispatch} from 'redux'

type Todos = {
    todos: Array<ITodo>
}

const mapStateToProps = (state:Todos)=>{
    console.log('state:',state);
    
    return {
        todos: state.todos
    }
}

const mapDispatchToProps = (dispatch:Dispatch)=>{
    return {
        add:()=>{
            
        }
    }
}

export default connect(mapStateToProps,mapDispatchToProps)
使用connect将redux和组件连接起来
  • 相当于一个高阶组件
//pages/todo/visibleList.tsx

import React, { Component } from 'react'
import TodoItem from '../../components/TodoItem'
import connect from './connect'
import { ITodo } from '../../store/types'

type IProps = {
    todos: Array<ITodo>
}
class VisibleList extends Component<IProps> {
    render() {
        return (
            <ul>
                {
                   //这里的props就是connect的结果,从reducers那里获取的
                   //此处的this.props.todos对应着combineReducers({todos}),名字以combineReducers对象的为准
                   this.props.todos.map(item=>{
                        return <TodoItem {...item}></TodoItem>
                    })
                }
            </ul>
        )
    }
}

export default connect(VisibleList)
事件派发改变store
  • 组件使用connect连接redux
  • 使用this.props.fn()来获取connect.tsx里的方法
  • connect里定义方法来触发store
//pages/todo/AddTodo.tsx

import React, { Component } from 'react'
import connect from './connect'
let textRef:React.RefObject<HTMLInputElement> = React.createRef();

type P = {
    add:(text:string)=>void
}
class AddTodo extends Component<P> {
    handleClick=()=>{
        //获取input里面的数据,有受控和非受控两种方式,ref为非受控非方式
        let txt = (textRef.current as HTMLInputElement).value
        //调用添加方法add,即是connect.ts中的add方法
        this.props.add(txt)
    }
    render() {
        return (
            <div>
                <input type="text" ref={textRef} />
                <button onClick={this.handleClick}>添加</button>
            </div>
        )
    }
}
export default connect(AddTodo)

//---------------------------------------------------

//pages/todo/connect.ts
import { connect } from 'react-redux'
import { ITodo } from '../../store/types'
import { Dispatch} from 'redux'

type Todos = {
    todos: Array<ITodo>
}

let id = 1;
const mapStateToProps = (state:Todos)=>{
    console.log('state:',state);
    return {
        todos: state.todos
    }
}

const mapDispatchToProps = (dispatch:Dispatch)=>{
    return {
        //组件通过this.props.add('123')的方式调用这个方法,同时传参进来
        add:(txt:string)=>{
            dispatch({ //事件派发给store中的ADD_TODO,同时传递参数
                type:"ADD_TODO",
                id: id++,
                text: txt
            })
        }
    }
}

export default connect(mapStateToProps,mapDispatchToProps)