React笔记

238 阅读12分钟

redux

redux三个特性

  1. single source of tree 中控数据源
  2. 可预测性 state + action = new state
  3. 纯函数更新Store 根据不同的action 执行不同的操作 返回新的state

创建store

​Store 就是用来维持应用所有的 state 树的一个对象。 改变 store 内 state 的惟一途径是对它 dispatch 一个 action

const store = createStore(reducer)

createStore构造函数提供了三个方法

  1. getState() //获取当前的数据
  2. dispatch(action) //往reducer 提交一个action
  3. subscribe(listener) //state发生改变 会调用该方法 执行里面的更新

Aciton

​描述了一个行为的数据结构

​Action 本质上是 JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action

{	
	type:ADD_TODO,
	text:"Build my first Redux app"
}

Reducer

Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。

//参数一  接受之前的state 没有的话 给一个默认值 {}
function  todoApp(state= {},action){
	switch(action.type){
            case:ADD_TODO:
                    //返回一个新的state  包含了旧数据
                return Object.assign({},state,{
                      //放入state中的key  为todos
                  todos:[
                    ...state.todos,
                    {text:action.text,complated:false}
                  ]
		})
            defalut:
             return state
    }
}

combineReducers

​接受多个reducer作为参数,形成的结果也是一个reducer,它会把action散发到不同的子reducer中

combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 [createStore] 方法(特别注意,使用该api时,每个reducer必须要有default的返回值)

import { combineReducers } from 'redux'
import todos from './todos'
import counter from './counter'

/*
    最终的数据格式为
       state :{
           todos:xxx
       },
       actions:todos
*/
export default combineReducers({
  todos,
  counter
})

bindActionCreators

​把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。同时使用 dispatch 对每个 action creator 进行包装,以便可以直接调用它们。

//自定义实现
//讲action 和dispatch 封装在一起
function addTodoWithDispatch(text){
	const action = {
		type:ADD_TODO,
		text
	}
	dispatch(action)
}

//简化写法 抽离addTodo 增加action
const boundAddTodo = text => dispatch(addTodo(text))

调用bindActionCreators实现

//bindActionCreators源码
function bindActionCreator(actionCreator,dispatch){
	return function(){
		return dispatch(actionCreator.apply(this,arguments))
	}
}

function bindActionCreators(actionCreators,dispatch){
	const keys = Object.keys(actionCreators);
	const boundActionCreators = {}
	for(let i = 0;i < keys.length;i++){
		const key = keys[i];
		const actionCreator  = actionCreators[key];
		if(typeof actionCreator === 'function'){
			boundActionCreators[key] = bindActionCreator(actionCreator,dispatch)
		}
	}
	return boundActionCreators
}

bindActionCreators使用详解

function plusOne(){
	return { type:"PULS_ONE"}
}
//参数1	接收一个方法 返回action 
//参数2 为store的 dispatch方法
//返回一个 方法  调用该方法即可
plusOne =  bindActionCreators(plusOne,store.dispatch)
//相当于 自动dispatch plusone的返回值

//好处是 在使用的时候 不需要知道store在哪 ,直接使用plusOne 这个方法就可以了

在react中使用redux

import { connect } from 'react-redux'
import {bindActionCreators} from 'redux'
class SlidePanel  extends Component{
			
}
function mapStateToProps(state){
	return {
		nextgen:state.nextgen,
		router:state.router
	}
}

function mapDispathToProps(dispatch){
	return {
            actions:bindActionCreators({...actions},dispatch)
	}
}

//通过高阶组件的形式 将state和dispatch 注入到组件中
export defalut connect(mapStateToProps,mapDispathToProps)(SlidePanel)


//app.js
//provider 会把store 传入到每个connect 容器组件
import {Provider}
function App(){
  let store = createStore(todoApp)
  return (
    <Provider store={store}>
      <App />
    </Provider>
}

redux中间件 middleware

  1. 截获action
  2. 发出action
//action
function gotoFetchData() {
  return (dispatch) => {
    dispatch({
      type: "FTCH_BEGIN",
    });

    const promise = new Promise((res, rej) => {
      const dorequest = fetch(
      "https://www.fastmock.site/mock/d6595c932da9f203e13525deb8d8eba6/api/getUserIngo"
      ).then((res) => res.json());
      dorequest.then(
        (res) => {
          dispatch({
            type: "FTCH_SUCCESS",
            data: res.data,
          });
        },
        (err) => {
          dispatch({
            type: "FTCH_FAILURE",
            data: res.data,
          });
        }
      );
    });

    return promise;
  };
}

//action reudcer
function asyThunk(state = {}, action) {
  console.log(action);
  switch (action.type) {
    case "FTCH_BEGIN":
      return {
        ...state,
        fetchDataListPending: true,
        fetchDataListError: null,
      };
    case "FTCH_SUCCESS":
      return {
        ...state,
        fetchDataListPending: false,
        fetchDataList: action.data,
        fetchDataListError: null,
      };
    case "FTCH_FAILURE":
      return {
        ...state,
        fetchDataListPending: false,
        fetchDataListError: action.data.error,
      };
    case "FTCH_ERROR":
      return {
        ...state,
        fetchDataListPending: false,
        fetchDataListError: null,
      };
    default:
      return state;
  }
}

//使用中间件 处理异步
const store = createStore(reducers, applyMiddleware(thunkMiddleware));

//reducer接受不同的状态 进行处理 
//action  返回promise  dispatch不同的type

异步action不是特殊的action,而是多个同步action的组合使用, 中间件在dispatcher中截获action做特殊处理

immer处理state 的不可变数据

redux-actions

创建reducer handleAction

// actionType
export const INCREMENT = 'INCREMENT'
/*
    参数一为type
    参数二为handle
    参数三为默认值
*/
import { handleAction } from 'redux-actions'
//需要注意的是 默认值和 handle里面的取值要一致,如果是对象 就要取对象的属性
 handleAction(
    INCREMENT,
    (state, action) => {
        return state + action.payload 
    },
    1
)

创建action createAction

/*
    返回数据格式为 
    { type: 'INCREMENT'}
    如果是调用时传了值,会增加一个属性
    payload: xx
*/
import { createAction } from 'redux-actions'
export const increment = createAction(INCREMENT)

// 使用bindActiveCreators 调用时
function mapDispatchToProps(dispatch) {
  return {
    // incrementReducer:bindActionCreators(increment,dispatch)
    actions: bindActionCreators({
      increment
    }, dispatch)
  };
}

createActions使用 创建多个action

/*
    ADD_TODO 会被转为 addTodo, 调用返回值跟上面单独创建一致,下划线形式会被转为驼峰
    也就是说 createActions 返回的是一个对象,会把对象的每一项单独创建成action,最后返回该对象
*/
const handleActions =  createActions({
    //单独直接创建 并且没有默认plaload
    ADD_TODO: todo => ( todo ),  
    //存在默认payload 为todo ,值为undefind
    SUB_TODO: todo => ( {todo} ), 
    // 创建时增加其他参数
    REMOVE_TODO: [
        todo => ({ todo }),
        ((todo, warn) => ({ todo, warn }))
    ]
})

handleActions的使用

//佘handleAction的使用类似,只是一次创建多个
const reducer = handleActions({
        INCREMENT: (state, action) => {
            return state + action.payload
        },
        DECREMENT: (state, action) => ({
            counter: state - action.payload
        })
   },
   0,
)

combineActions使用

/*
    combineActions 的作用是同时订阅多个actionType
*/
import { combineReducers } from 'redux'

import { handleAction, handleActions, combineActions } from 'redux-actions'

import { increment, decrement } from './actions'

const reducer = handleAction(combineActions(increment, decrement), {
    next: (state, { payload }) => {
        console.log(state, payload);
        return { count: state.count + payload }
    },
    throw: () => ({ count: 0 }),
}, { count: 10 })

const reduce2 = handleActions(
    {
        [combineActions(increment, decrement)](state, { payload }) {
            return { count: state.count + payload }
        }
    },
    { count: 10 }
)

export default combineReducers({
    reducer,
    reduce2
})

redux-thunk

import thunkMiddleware from 'redux-thunk'
const store = createStore(reducer, applyMiddleware(thunkMiddleware))

redux-thunk主要的功能就是可以让我们dispatch一个函数,而不只是普通的 Object

异步 Action

​当调用异步 API 时,有两个非常关键的时刻:发起请求的时刻,和接收到响应的时刻(也可能是超时)。

​这两个时刻都可能会更改应用的 state;为此,你需要 dispatch 普通的同步 action。一般情况下,每个 API 请求都需要 dispatch 至少三种 action:

  • 一种通知 reducer 请求开始的 action。

    对于这种 action,reducer 可能会切换一下 state 中的 isFetching 标记。以此来告诉 UI 来显示加载界面。

  • 一种通知 reducer 请求成功的 action。

    对于这种 action,reducer 可能会把接收到的新数据合并到 state 中,并重置 isFetching。UI 则会隐藏加载界面,并显示接收到的数据。

  • 一种通知 reducer 请求失败的 action。

    对于这种 action,reducer 可能会重置 isFetching。另外,有些 reducer 会保存这些失败信息,并在 UI 里显示出来。

为了区分这三种 action,可能在 action 里添加一个专门的 status 字段作为标记位:

{ type: 'FETCH_POSTS' }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }

又或者为它们定义不同的 type:

{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }

异步action使用,在发起的时候先dispatch一次触发loading,然后接口请求完成再次发起dispatch,又或者是error时触发失败的dispatch

function fetchPosts(subreddit) {  
    return dispatch => {    
        dispatch(requestPosts(subreddit))    
             return fetch(`http://www.reddit.com/r/${subreddit}.json`)   
            .then(response => response.json())      
            .then(json => dispatch(receivePosts(subreddit, json)))
            .catch(err=>dispatch(errorPosts(subreddit)))  }}

需要使用middleware支持redux异步数据流,可以使用redux-thunk,redux-promise

const store = createStore(reducers,applyMiddleWare(thunk))

redux-saga

我理解为独立线程,专门处理异步等副作用事件,跟reducer类似,和其他同步的reducer分开,专门处理异步的dispatch,

import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'

import reducer from './reducers'
import mySaga from './sagas'

// 创建saga中间件实例
const sagaMiddleware = createSagaMiddleware()
// mount it on the Store
const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)

// 创建store启动saga进程
sagaMiddleware.run(mySaga)
//sagas.js

//call 调用异步方法
//put 发布消息,同于dispatch
//takeEvery 执行任务队列
//all  类似promise.all,按顺序执行任务
import { call, put, takeEvery, all } from 'redux-saga/effects'

//普通saga,跟普通的函数没区别,在run 的时候会就触发
function* helloSaga() {
    console.log('hello saga');
}

//模拟请求数据
function getFetchData() {
    return Promise.resolve({
        res: {
            name: 1
        }
    })
}

//处理异步请求,拿到结果后发布dispatch
function* fetchData() {
    try {
        const data = yield call(getFetchData)
        yield put({
            type: "FETCH_SUCCEEDED", data
        })
    } catch (error) {
        console.log(error, '2222');
        yield put({
            type: "FETCH_SUCCEEDED", error: { code: -1 }
        })
    }
}

//监听 FETCH_DATA 事件
function* watchFetchData() {
    yield takeEvery('FETCH_DATA', fetchData)
}
//启动函数, 在使用all之后,会按顺序执行队列里面的任务
export default function* rootSaga() {
    yield all([
        helloSaga(),
        watchFetchData()
    ])
}

react-router

  1. 单页应用需要进行页面切换
  2. 通过URL可以定位到页面
  3. 更有语义的组织资源,(实现资源隔离)

实现原理

  • 路由定义,形成路径和资源加载(组件)的映射关系
  • 通过react-router进行解析,根据url 返回不同的组件
  • 定义组件容器,router会对组件容器进行控制,当url变化,只是组件容器进行变化

代码实现

<Router>		
    <div>				
        <ul id='menu'>						
            <li><Link to='/home'>Home</Link></li>				
         </ul>		
        </div>		
     <div id='page-container'>				
      <Route path='/home' component={Home}></Route>		
      </div>
  </Router>

react-router特性

  1. 声明式路由定义,(可以像html标签一样随意使用,比较直观)
  2. 动态路由,(在页面render的时候才会实时解析配置),

react-router实现方式

三种配置模式

  1. URL路径,(通过url路径匹配,直接是斜杠路径变更) /router/home
  2. hash路由,改变hash值来实现路径匹配, /#/router/home, 可以支持低版本浏览器
  3. 内存路由, MemoryRouter, 路由变化不会体现在浏览器,而是在内存中
<HashRouter><HistoryRouter><MemoryRouter>

基于路由配置进行资源组织

  1. 实现业务逻辑的松耦合
  2. 易于扩展,重构和维护
  3. 路由层面实现lozy load

ReactRouter Api

  1. <Link> 普通连接,不会触发浏览器刷新
  2. <NavLink> 类似<Link> 但是会添加选中状态
  3. <Prompt> 满足条件时 提示用户是否离开当前页面
  4. <Ridirect> 重定向当前页面 例如登录判断
  5. <Route> 路由配置的核心标记,路径匹配时显示对应组件
  6. <Switch> 只显示第一个匹配的路由

通过url传递参数

  1. 通过url传递参数

    <Route path='/topic/:id' >
    
  2. 获取url参数

    this.props.match.params
    

嵌套路由

//parent
<HashRouter>  
    <div>    
        <Link>parent 1</Link>  
    </div>  
    <Route path='/children' component={Children}></Route>
</HashRouter>
//children 
<HashRouter>  
    <div>    
        <Link>children 1</Link>  
    </div>  
    <Route path='/children/info' component={ChildrenInfo}></Route>
</HashRouter>

Next.js

项目配置

基于react改造为next

npm install next react react-dom# oryarn add next react react-dom
"scripts": {  "dev": "next dev",  "build": "next build",  "start": "next start"}

直接构建next 脚手架

npx create-next-app# oryarn create next-app

页面(page) 根据其文件名与路由关联。例如,pages/about.js 被映射到 /about。甚至可以在文件名中添加动态路由参数。

components -> 	组件存放目录 固定配置
pages ->  页面级组件存放位置 固定配置	
about.js ->  /about路由
styles ->  样式文件存放位置
static或者public ->  静态资源存放位置

路由构建

  1. 使用"next/link" 定义链接

  2. 点击链接时页面不会刷新

  3. 使用prefetch 预加载目标资源

    <Link prefetch href='/about' >  使用该属性 会预加载当前路由 对应的组件
    
  4. 使用replace 属性替换URL

动态加载组件

import dynamic from "next/dynamic";
const Dync = dynamic(import("../components/info"), {  loading: () => <p>loading....</p>,});

请求数据

getStaticProps什么时候应该使用

您应在以下情况下使用getStaticProps

  • 呈现页面所需的数据可在构建时在用户请求之前获得。
  • 数据来自无头CMS。
  • 数据可以被公共缓存(不是特定于用户的)。
  • 该页面必须预渲染(对于SEO)并且必须非常快-getStaticProps生成HTML和JSON文件,CDN可以将它们都缓存以提高性能。
function Blog({ posts }) {  
return (    
    <ul>      
    {posts.map((post) => (        
        <li>{post.title}</li>      
      ))}   
    </ul>  )}
  export async function getStaticProps() {  
      const res = await fetch('https://.../posts')  
      const posts = await res.json()  
      if (!posts) {    
          return {      notFound: true,    }  
      }  
      return {    
          props: {      
          posts,			
          //这里的key 为什么 组件接受的prop 就是什么    },  
         }
      }
   export default Blog

React项目结构设计

可以分模块(featrue) ,讲视图.reducer.router按功能划分为不同文件,最后通过不同的root讲所有的组合起来,这样只需要删掉对应的文件夹,即可去掉部分功能,实现低耦合

React重点知识

React生命周期

当前情况=> app.js (父组件), List.js(子组件)

//组件第一次加载
par constructor  
//(par) 父组件  没有前缀表示子组件
parRenderConstructorRender
componentDidMountpar 
componentDidMount
//组件更新  父组件state更新的情况
par render 
props render

父组件渲染=> 子组件render=>子组件挂载完成=>父组件挂载完成

父组件render=>子组件接受到props=>子组件更新

React 构成

  • 基础模块, react 基础 API 及组件类,组件内定义 render 、setState 方法和生命周期相关的回调方法。
  • 渲染模块,针对不同宿主环境采用不同的渲染方法实现,如 react-dom, react-webgl, react-native, react-art, 依赖 react-reconciler, 注入相应的渲染方法到 reconciler 中。
  • 核心模块,负责调度算法及 Fiber tree diff, 连接 react 及 renderer 模块,注入 setState 方法到 component 实例中,在 diff 阶段执行 react 组件中 render 方法,在 patch 阶段执行 react 组件中生命周期回调并调用 renderer 中注入的相应的方法渲染真实视图结构。

React 初始化流程

​ JSX 会经过 babel 编译成 React.createElement 递归调用的表达式,React.createElement 会在 render 函数被调用的时候执行,换句话说,当 render 函数被调用的时候,会返回一个 element(组成虚拟 DOM 树的节点)。

自定义组件 mounted过程

  1. 得到实例化 App 对象 instance
  2. renderedElement = instance.render();
  3. 初始化 renderedElement 得到 child
  4. child.mountComponent(container)
  5. 在第一步得到 instance 对象之后,就会去看 instance.componentWillMount 是否有被定义,有的话调用,而在整个渲染过程结束之后调用 componentDidMount。

setState 流程

  • newState 存入 pendingState 队列
  • 根据一个变量 isBatchingUpdates 判断是直接更新 state 还是放到队列中。也就是决定是将组件放到 dirtyComponents 中,还是遍历 dirtyComponents,调用 updateComponent,去更新 state 或者 props。
  • isBatchingUpdates 默认是 false,React 在调用事件处理函数之前就会调用 batchedUpdates 改变值,以此让 state 不会立即更新。

事件机制

React 事件并没有绑定在真实的 Dom 节点上,而是通过事件代理,在最外层的 document 上对事件进行统一分发,原生事件在目标阶段执行,React 在冒泡阶段执行。

组件挂载更新时,给 document 注册原生事件回调为 dispatchEvent (统一的事件分发机制)。

事件初始化,添加到 listenerBank,结构是:

 listenerBank[registrationName][key]

触发事件时:

  • 触发 document 注册原生事件的回调 dispatchEvent
  • 获取到触发这个事件最深一级的元素
  • 遍历这个元素的所有父元素,依次对每一级元素进行处理。
  • 构造合成事件。
  • 将每一级的合成事件存储在 eventQueue 事件队列中。
  • 遍历 eventQueue。
  • 通过 isPropagationStopped 判断当前事件是否执行了阻止冒泡方法。
  • 如果阻止了冒泡,停止遍历,否则通过 executeDispatch 执行合成事件。
  • 释放处理完成的事件。