react 项目学习

react

1-2 前置准备

开发环境:

  Node.js(v8.2+)

  NPM(v5.2+)

  Visual Studio Code(VS Code)

VS Code常用插件:

  Prettier-Code formatter   格式化代码

  Reactjs code snippets  快速生成 react 常用模块化代码

  Auto Rename Tag  对相关标签重命名时,对应标签相应改变

主要依赖库版本(需高于以下版本):

  React: ^16.4.1

  Redux: ^4.0.0

  React Redux: ^5.0.7

  React Router: ^4.3.1

2-1 创建项目结构

React项目脚手架: create-react-app

  零配置创建 React 应用 (不需要配置babel和webpac等)

  构建: JS、CSS、图片 (资源打包构建)

  开发效率: 自动刷新、代理转发、单元测色等 

create-react-app 的使用

  新建项目:npx create-react-app [项目名] (npm >= 5.2)

  在命令面板安装 code 就可以在终端中使用 code 可以在vs code 中快速打开项目

package.json

{

"name": "dz-app",

"version": "0.1.0",

"private": true,

"dependencies": {

"react": "^16.10.1",

"react-dom": "^16.10.1",

"react-scripts": "3.1.2" //其他的相关依赖都封装到了react-script中,webpack。babel等

},

"scripts": {

"start": "react-scripts start",

"build": "react-scripts build",

"test": "react-scripts test", //测试

"eject": "react-scripts eject" //将原本相关配置的封装弹射出来,将wepack.cofig在项目中显现出来

},

 使用 Mock 数据

方式一: 代理到 mock 服务器 (通过开启一台mock服务器,将mock数据放到该服务器上,将前端请求转发到这个服务器上)

  npm install -g serve 安装服务

  在package.json 中配置

  “”proxy“: {

    "/api": {

      "target": "http://localhost:5000"

    }

   }  

方式二:直接将 mock 数据放到项目 public 文件夹 (public 中新建 mock 文件夹 > data.json 文件) 通过localhost:3000/mock/data.json 就能访问

 原因是:public 文件夹的静态资源是不会被构建的,打包后直接放到项目中使用的

3-1 组件划分

组件划分原则

  解耦:降低单一模块/组件的复杂度

  复用:保证组件一致性,提升开发效率

  组件颗粒度需要避免过大或过小

3-2 编写静态组件

  开发过程解耦:静态页面和动态交互

  组件开发顺序:自上而下 or 自下而上

  App -> TodoList -> Todo -> AddTodo -> Footer

3-3 如何设计 State

什么是 State?

  代表 UI 的完整且最小状态集合

如何判断一个变量是否是 State

  是否通过父组件props 传入?   通过父组件传入的不是

  是否不会随着时间、交互操作变化?  不会随时间、交互操作的不是

  是否可以通过其他 state 或 props 计算得到?  可通过其他 state 或 props 计算得到的不是,有冗余,不符合最小状态集合

3-4 分析 State 保存位置

State 的双层含义

  代表应用UI 的所有状态的集合

  状态集合中的每一部分(待办事项列表、新增输入框文本、筛选条件)

确定依赖 state 的每一个组件

如果某个 state 被多个组件依赖,寻找共同的父组件(状态上移)

3-5 添加交互行为

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

4-1 Redux 基本思想

4-2 设计应用 Sate

  集中管理,全局唯一

  不可变性

  定义方式同React State  

4-3 定义Action

  描述如何修改状态

  JSON对象,type属性必需

  发送: store.dispatch

4-4 action的处理器: reducer

4-5 reducer 拆分

  便于扩展和维护

  合并API: combineReducers

4-6 创建store

4-7 集成react-redux(容器型组件拆分)

  向根组件注入Store -> Provider组件

  连接React 组件和Redux状态层 -> connect

  获取React 组件所需的 State和 Actions -> map api

4-8 集成react-redux(容器型编写)

4-9 集成react-redux 回顾

 

5-1 项目结构组件方式

  Redux 项目结构组织方式

    按照类型

    按照功能模块

    Ducks(鸭子)

 

按照类型 ( 指的是一个文件在项目中充当的角色类型,即这个文件是一个组件、容器、reducer、action,会放在不同的文件下,redux官网示例采用的结构 )

缺点:开发一个功能的时候需要频繁更换目录修改不同的文件,当项目结构逐渐的变大就会变的不方便

   actions/            components/

    --action1.js            --component1.js

    --action2.js            --component2.js

    

    reducers/           containers/

    --reducers.js          --container1.js

    --reducer2.js          --container2.js

按照功能模块 ( 一个功能模块对应一个文件夹 )

优点: 方便开发,易于功能扩展

缺点: redux 会将整个应用的状态作为一个Store来管理,也就是作为全局的state来管理,不同的功能的模块之间共享这个全局的state的部分状态,可能不同功能模块之间出现耦合

 feature1/          feature2/

  -- components/        -- components/

  -- Comtainer.js         -- Container.js

  -- actions.js           -- actions.js

  -- reducer.js          -- reducer.js

Ducks

  最初来源:https://github.com/erikras/ducks-modular-redux

  提倡将相关的reducer、 action types、 action 写到一个文件里、本质上以应用的状态作为模块的划分依据,不是以界面功能作为依据,这样管理相同的依赖都在同一文件中,不管哪个容器组件需要使用,只需引用即可

  reducers、action types、actions 组合到一个文件中,作为独立模块

  划分模块依据: 应用状态State,而不是界面功能

ducks

  src 

   components/  (放置整个应用级别的通用组件,通用的loading,错误弹框等)

   containers/ (按照页面的功能模块划分,每个功能模块对应一个文件夹,功能模块下又会有component文件夹,这里的component夹 存放的是这个功能模块的专用组件,他们的复用性相对较弱,只在这个功能模块下使用,而index.js 是feature文件夹 对外暴露的接口,是容器型组件,这种项目结构组织方式,将视图层和状态层彻底解耦,甚至可将前端开发人员 分为两部分 视图层 和 redux状态层的开发,这种情况下feature1 和 feature2 每个功能模块 只需要知道redux 中的哪些模块,然后引用这些模块就可以了)

    feature1/

      compinents/  (这里的component夹 存放的是这个功能模块的专用组件,他们的复用性相对较弱,只在这个功能模块下使用)

      index.js/  (而index.js 是feature文件夹 对外暴露的接口,是容器型组件)

    feature2/

   redux/n

    index.js

    module1.js (对应redux 项目结构组织当中的一个模块, 包含了所使用的actions、reducer、action creator,所以这种项目结构组织方式会根据应用状态来划分)

    module2.js

   index.js

还需对模块进行调整

// Action Creators 会将action 定义到一个命名空间当中

export const actions = {

  loadWidget: function loadWidget() {

    retrun { type: LOAD }

  }

  createWidget: 

  updateWidget: updateWidget

}

5-2 State 设计原则

 设计redux state的时候 常见的两种错误

  以API 为设计State 的依据

  以页面UI 为设计的State 的依据

  eg:

//获取博客列表: /posts

[

{

"id": 1,

"title": "Blog Title",

"create_time": "2017-01-10T23:07:43.248Z",

"author": {

"id": 81,

"name": "Mr Shelby"

}

}

...

]

// 获取博客详情: /posts/{id}

{

"id": 1,

"title": "Blog Title",

"create_time": "2017-01-10T23:07:43.248Z",

"author": {

"id": 81,

"name": "Mr Shelby"

},

"content": "Some really short blog content. "

}

// 获取博客的评论: /posts/{id}/comments

[

{

"id": 41,

"author": "Jack",

"create_time": "2017-01-11T23_07:43.248Z",

"content": "Good article!"

}

...

]

 以api 为设计的state

  有很多数据重复(title、create_time),有些数据类型为数组,遍历不方便, 因为api 是基于服务器端的逻辑进行设计的,而不是基于前端应用的状态进行设计的

{

“posts”: [

{

"id": 1,

"title": "Blog Title",

"create_time": "207-01-10T23:07:43.248Z",

"author": {

"id": 81,

"name": "Mr Shelby"

}

},

...

],

"currentPost": {

"id": 1,

"title": "Blog Title",

"create_time": "2017-01-10T23:07:43.248Z",

"author\': {

"id": 81,

"name": "Mr Shelby"

},

"content": "Some really short blog content. "

},

"currentComments": [

{

"id": 1,

"author": "Jack",

"create_time": "2017-01-11T23:07:43.284Z",

"content": "Good article!"

},

...

]

}

 

以页面UI 为设计的State 的依据、

  会造成存储的浪费,也会存在数据不一致的风险

{

"all": [

{

"id": 1,

"text": "todo 1",

"completed": false

},

{

"id": 2,

"text": "todo 2",

"completed" true

}

],

"uncompleted": [

{

"id": 1,

"text": "todo 1",

"completed": false

}

],

"completed": [

{

"id": 2,

"text": "todo 2",

"completed": false

}

]

}

 

像设计数据库一样设计State

  设计数据库基本原则

  数据按照领域分类,存储在不同的表中,不同的表中存储的列数据不能重复

  表中每一列的数据都依赖于这张表的主键

     表中除了主键以外的其他列,互相之间不能有直接依赖关系

 设计State原则

 数据按照领域把整个应用的状态按照领域 分成若干子State, 子State之间不能保存重复的数据

 表中State 以键值对的结构存储数据,以记录的 key/ID  作为记录的索引,记录中的其他字段都依赖于索引

    State 中不能保存可以通过已有数据计算而来的数据,即State 中的字段不互相依赖

{

"posts": {

"1": {

"id": 1,

"title": "Blog Title",

"content": "Some really short blog content.",

"created_at": "2016-01-11T23:07:43.248Z",

"author": 81,

"comments": [

352

]

},
"3": {

  }

...

},

"postIds": [1, ..., 3], //存了每个博客的Id值

"comments": {

"352": {

"id": 352,

"content": "Good article!",

"author": 41

},

...

},

"authors": {

"41": {

"id": 41,

"name": "Jack"

},

"81": {

"id": 81,

"name": "Mr Shelby"

},

...

}

}

 补充

  State 应该尽量扁平化(避免嵌套层级过深)

  UI State: 具有松散性

 

 5-3 selector 函数

选择器函数,作用是从Redux State 中读取部分数据,给Container Components 来使用

 

5-4 深入理解前端状态管理思想

5-5 Middleware

示例: redux-thunk

增强store dispatch的能力

5-6 Store Enhancer

增强redux store的功能

createStore(reducer,[preloadedState], [enhancer])

5-7 常用集成库 Immutable.js

  简化操作和提高效率

5-9 常用集成库 Reselect

 

6-1 客户端路由和服务端路由

MPA 和 SPA

 MPA(Multiple Page App):多页面应用  每当url 发送变化的时候都会返回新的html,打开network 查看请求html信息是变化的

 

  优点:不同的url,返回不同的html,利于搜索引擎爬虫爬取,有利于SEO

 SPA(Single Page App):单页面应用   和多页面应用相反

  优点:url发生变化的时候,公用的页面组件不会重新渲染,所以不会像多页面应用从白屏在到页面显示,用户体验更好, 性能更好

  缺点:对SEO不友好

服务端路由

  多页面应用更多依赖于服务端的功能

客户端路由

  SPA 依赖于客户端路由

6-2 Router 相关库

<BrowserRouter>

  HTML5 history API ( pushState, repalceState等 )

  需要Web服务器额外配置 

<HashRouter>

  使用url 的 hash 部分作为路由信息

  主要为了兼容老版本浏览器

6-4 路由匹配

6-5 路由渲染组件的方式

  <Route component> 

  <Route render>

  <Route children>

7-1 前端架构是什么?

软件架构是什么?

传统架构

  抽象(将来源于真实业务场景中的需求功能进行抽象,用设计模式和算法去描述)

  解耦(模块化)

  组合(将解耦后的各个模块按照模块之间的接口规范结合在一起)

由抽象、解耦、组合这三个层次构成一般意义上的软件架构

-----------------------------------------------------------------------------------------------

由另一层次上看什么是架构

架构(整个软件最高层次的抽象) =>  框架 设计模式(在选取不同的框架和设计模式实现架构)  => 代码(实现框架和设计模式)

------------------------------------------------------------------------------------------------

前端架构的特殊性

  前端不是一个独立的子系统,又横跨整个系统

  分散性:前端工程化

  页面的抽象、解耦、组合

和传统架构不同,前端结构主要解决的两个问题: 前端工程化 和 页面的抽象、解耦、组合

 

前端工程化(具体要让我们的前端项目可控。高效)

  可控:脚手架、开发规范等

  高效:框架、组件库、Mock平台、构建部署工具等

前端抽象问题

  页面UI抽象:组件

  统用逻辑抽象:领域实体、网络请求、异常处理等

-----------------------------------------------------------------------------

7-2 案例分析

功能路径

理解和梳理业务需求是设计软件架构的第一步,通常我们会根据需求文档和界面原型图来梳理业务需求

  展示:首页  -> 详情页

  搜索:搜索页 -> 结果页

  购买:登录 -> 下单 -> 我的订单 -> 注销

--------------------------------------------------------------------------------

7-3 前端架构之工程化的准备1

技术选型和项目脚手架

 

技术选型考虑的三要素:

  业务满足程度

  技术栈的成熟度(使用人数、周边生态、产库维护等)

  团队的熟悉度

 

技术选型:

  UI层:React

  路由:React Router

  状态管理:Redux

脚手架

  Create React App (npx create-react-app my-app)

7-4 前端架构之工程化准备2

基本规范

  目录结构

  构建体系

  Mock数据

目录结构

  public

    mock

      products

        likes.json

  src

    components(展示型组件)

      Header

        style.css

        index.js

    containers(容器型组件)

      App  

        style.css

        index.js

      Home

        components

        style.css

        index.js

    redux

      modules

      middleware

    utils

    images

   index.html

   package.json

删除多余部分: 将App.js 、App.css 移到 container > App

logo.svg   serviceWorker.js 删除

 ---------------------------

7-5 前端架构抽象1:

状态模块定义

  商品、店铺、订单、评论(领域实体)

  各页面UI 状态(各页面具体的UI 状态,eg:多选框是否选中状态,输入框信息怎样,loading弹出框是否弹出)

  前端基础状态:登录态、全局异常信息 (特殊UI 状态,各个页面共享)

Redux 模块分层 (分两层:领域实体 和 状态)

容器组件  ->   页面状态  通用状态  -> 领域状态

  redux

    modules

      entities

        comments.js

        index.js(聚合 领域状态)

        orders.js

        products.js

        shops.js

      home.js

      detail.js

      app.js

      index.js (聚合所有 领域状态 和 UI 状态)  

      store.js

orders.js、comments.js、products.js、shops.js

const reducer = (state = {}, action) => {

return state

}

export default reducer

index.js

import { combineReducers } from "redux"

import products from "./products"

import shops from "./shops"

import orders from "./orders"

import comments from "./comments"

// 合并领域状态

const rootReducer = combineReducers({

products,

shops,

orders,

comments

})

index.js (聚合所有 领域状态 和 UI 状态)

import { combineReducers } from "redux"

import entities from "./entities"

import home from "./home"

import detail from "./detail"

import app from "./app"

//合并成根reducer

const rootReducer = combineReducers({

entities,

home,

detail,

app

})

store.js

import { createStore, applyMiddleware } from "redux"

import thunk from "redux-thunk"

import rootReducer from "./modules"

if ( process.env.NODE_ENV !== "production" && window.__REDUX_DEVTOOLS_EXTENSION__ ) {

const composeEnhancers =

window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;

store = createStore(rootReducer, applyMiddleware(thunk) )

} else {

let store = createStore(rootReducer, applyMiddleware(thunk) )

}

export default store

index.js

import React from \'react\';

import ReactDOM from \'react-dom\';

import \'./index.css\';

import { Provider } from \'react-redux\'

import App from \'./containers/App\';

import store from \'./redux/store\'

ReactDOM.render(

<Provider store={store}>

<App />

</Provider>,

document.getElementById(\'root\'));

 

7-6 前端架构之抽象2:网络请求层封装(redux-thunk)

utils

  request.js

  url.js

抽象2: 网络请求层

  常规使用方式

request.js

const headers = new Headers({

"Accept": "application/json",

"Content-Type": "application/json"

})

function get(url) {

return fetch(url, {

method: "GET",

headers: headers

}).then(response => {

handleResponse(url, response)

}).catch(err => {

console.error(`Request failed. Url = ${url}. Message=${err}`)

return Promise.reject({error: {message: "Request failed."}})

})

}

function post(url, data) {

return fetch(url, {

method: "POST",

headers: headers,

body: data

}).then(response => {

handleResponse(url, response)

}).catch(err => {

console.error(`Request failed. Url = ${url}. Message=${err}`)

return Promise.reject({error: {message: "Request failed."}})

})

}

function handleResponse(url, esponse) {

if(response.status === 200) {

return response.json()

} else {

console.error(`Request failed. Url = ${url}`)

return Promise.reject({error: {message: "Request failed due to server error"}})

}

}

export { get, post }

url.js

export default {

getProductList: (rowIndex, pageSize) =>

`/mock/products/likes.json?rowIndex=${rowIndex}&pageSize=${pageSize}`,

}

home.js

import { get } from "../../utils/request"

import url from "../../utils/url"

export const types = {

FETCH_LIKES_REQUEST: "HOME/FETCH_LIKES_REQUEST", //获取猜你喜欢请求

FETCH_LIKES_SUCCESS: "HOME/FETCH_LIKES_REQUEST", //获取猜你喜欢请求成功

FETCH_LIKES_FAILURE: "HOME/FETCH_LIKES_REQUEST", //获取猜你喜欢请求失败

}

export const actions = {

loadLikes: () => {

return (dispath, getState) => {

dispath( fetchLikesRequest() )

return get( url.getProductList(0, 10) ).then(

data => {

dispath(fetchLikesSuccess(data))

},

error => {

dispath(fetchLikesFailure(error))

}

)

}

}

}

const fetchLikesRequest = () => ({

type: types.FETCH_LIKES_REQUEST

})

const fetchLikesSuccess = (data) => ({

type: types.FETCH_LIKES_SUCCESS,

data

})

const fetchLikesFailure = (error) => ({

type: types.FETCH_LIKES_FAILURE,

error

})

const reducer = (state = {}, action ) => {

switch (action.type) {

case types.FETCH_LIKES_REQUEST:

//todo

case types.FETCH_LIKES_SUCCESS:

//todo

case types.FETCH_LIKES_FAILURE:

//todo

default:

return state

}

return state

}

export default reducer

7-8 前端架构之抽象2:网络请求层封装(redux中间件)

 网络请求层

  常规使用方式

  使用redux 中间件封装

 

使用redux 中间件封装

 redux

  middleware

    api.js

  modules

    entities

      products.js

products.js

export const schema = {

name: \'products\',

id: \'id\'

}

const reducer = (state = {}, action ) => {

return state

}

export default reducer

 

api.js

import { type } from "os"

// 经过中间处理的action 所具有的标识

export const FETCH_DATA = \'FETCH DATA\'

export default store => next => action => {

const callAPI = action[FETCH_DATA]

if(typeof callAPI === \'undefined\') {

return next(action)

}

const { endpoint, schema, types } = callAPI

if (typeof endpoint !== \'string\') {

throw new Error(\'endponit必须为字符串类型的URL\')

}

if(!schema) {

throw new Error(\'必须指定领域实体的schema\')

}

if(!Array.isArray(types) && types.length !== 3) {

throw new Error(\'需要指定一个包含了3个action type 的数组\')

}

if(!type.every(type => typeof === \'string\')) {

throw new Error(\'action type必须为字符串类型\')

}

const [requestType, successType, failureType] = types

next({type: requestType})

return FETCH_DATA(endpoint, schema).then(

response => next({

type: successType,

response

}),

error => next({

type: failureType,

error: error.message || \'获取数据失败\'

})

)

}

7-9  前端架构之抽象2:网络请求层封装(redux中间件)

api.js

//import { type } from "os"

import { get } from "../../utils/request"

import { normalize } from "path"

// 经过中间处理的action 所具有的标识

export const FETCH_DATA = \'FETCH DATA\'

export default store => next => action => {

const callAPI = action[FETCH_DATA]

if(typeof callAPI === \'undefined\') {

return next(action)

}

const { endpoint, schema, types } = callAPI

if (typeof endpoint !== \'string\') {

throw new Error(\'endponit必须为字符串类型的URL\')

}

if(!schema) {

throw new Error(\'必须指定领域实体的schema\')

}

if(!Array.isArray(types) && types.length !== 3) {

throw new Error(\'需要指定一个包含了3个action type 的数组\')

}

if(!type.every(type => typeof === \'string\')) {

throw new Error(\'action type必须为字符串类型\')

}

const actionWith = data => {

const finalAction = {...action, ...data}

delete finalAction[FETCH_DATA]

return finalAction

}

const [requestType, successType, failureType] = types

next(actionWith({type: requestType}))

return FETCH_DATA(endpoint, schema).then(

response => next(actionWith({

type: successType,

response

})),

error => next(actionWith({

type: failureType,

error: error.message || \'获取数据失败\'

}))

)

}

//执行网络请求

const fetchData = (endpoint, schema) => {

return get(endpoint).then(data => {

return normalizeData(data, schema)

})

}

//根据schema, 将获取的数据扁平化处理

const normalizeData = (data, schema) => {

const {id, name} = schema

let kvObj = {}

let ids = []

if (Array.isArray(data)) {

data.forEach(item => {

kvObj[item[id]] = item

ids.push(item[id])

})

} else {

kvObj[data[id]] = data

ids.push(data[id])

}

return {

[name]: kvObj,

ids

}

}

home.js

import { get } from "../../utils/request"

import url from "../../utils/url"

import { FETCH_DATA } from "../middleware/api"

import { schema } from "./entities/products"

export const types = {

FETCH_LIKES_REQUEST: "HOME/FETCH_LIKES_REQUEST", //获取猜你喜欢请求

FETCH_LIKES_SUCCESS: "HOME/FETCH_LIKES_REQUEST", //获取猜你喜欢请求成功

FETCH_LIKES_FAILURE: "HOME/FETCH_LIKES_REQUEST", //获取猜你喜欢请求失败

}

export const actions = {

loadLikes: () => {

return (dispatch, getState) => {

const endpoint = url.getProductList(0, 10)

return dispatch(fetchLikes(endpoint))

}

},

}

const fetchLikes = (endpoint) => ({

[FETCH_DATA]: {

types: [

type.FETCH_LIKES_REQUEST,

type.FETCH_LIKES_SUCCESS,

type.FETCH_LIKES_FAILURE

],

endpoint,

schema

},

})

const reducer = (state = {}, action ) => {

switch (action.type) {

case types.FETCH_LIKES_REQUEST:

//todo

case types.FETCH_LIKES_SUCCESS:

//todo

case types.FETCH_LIKES_FAILURE:

//todo

default:

return state

}

return state

}

export default reducer

products.js

export const schema = {

name: \'products\',

id: \'id\'

}

const reducer = (state = {}, action ) => {

if(action.response && action.response.products) {

return {...state, ...action.response.products}

}

return state

}

export default reducer

store.js

import { createStore, applyMiddleware } from "redux"

import thunk from "redux-thunk"

import api from "./middleware/api"

import rootReducer from "./modules"

let store

if ( process.env.NODE_ENV !== "production" && window.__REDUX_DEVTOOLS_EXTENSION__ ) {

const composeEnhancers =

window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;

store = createStore(rootReducer, applyMiddleware(thunk, api) )

} else {

store = createStore(rootReducer, applyMiddleware(thunk, api) )

}

export default store

 

7-10 前端架构之抽象3:通用错误处理

  错误信息组件

  错误状态

src

  components

    ErrorToast

      style.css

      index.js

  index.js

import React, {Component} from \'react\'

import "./style.css"

class ErrorToast extends Component {

render() {

const { msg } = this.props

return (

<div className="errorToast">

<div className="errorToast_text">

{msg}

</div>

</div>

)

}

componentDidMount() {

this.timer = setTimeout( () => {

this.props.clearError()

}, 3000 )

}

componentWillUnmount() {

if (this.timer) {

clearTimeout(this.timer)

}

}

}

export default ErrorToast

app.js

  

const initialState = {

error: null

}

export const types = {

CLEAR_ERROR: "APP/CLEAR_ERROR"

}

//action creators

export const actions = {

clearError: () => ({

type: types.CLEAR_ERROR

})

}

const reducer = (state = initialState, action ) => {

const { type, error } = action

if (type === types.CLEAR_ERROR) {

return {...state, error: null}

} else if (error) {

return {...state, error: error}

}

return state

}

export default reducer

// selectors

export const getError = (state) => {

  return state.app.error

}

 

 App

  index.js

index.js

  

import React from \'react\';

import { bindActionCreators } from \'redux\'

import { connect } from \'react-redux\'

import ErrorToast from "../../components/ErrorToast"

import { actions as appActions, getError } from \'../../redux/modules/app\';

import \'./style.css\';

function App() {

render () {

const {error, appActions: {clearError} } = this.props

return (

<div className="App">

{

error ? <ErrorToast msg={error} clearError={ clearError } /> : null

}

</div>

)

}

}

const mapStateToProps = () => {

return {

error: getError(state)

}

}

const mapDispatchToprops = (dispatch) => {

return {

error: getError(state)

}

}

export default connect(mapStateToProps, mapDispatchToprops)(App);

 

8-1 页面分析和组件划分

首页开发

  

以上是 react 项目学习 的全部内容, 来源链接: utcz.com/z/384423.html

回到顶部