redux
redux三个特性
- single source of tree 中控数据源
- 可预测性 state + action = new state
- 纯函数更新Store 根据不同的action 执行不同的操作 返回新的state
创建store
Store 就是用来维持应用所有的 state 树的一个对象。 改变 store 内 state 的惟一途径是对它 dispatch 一个 action
const store = createStore(reducer)
createStore构造函数提供了三个方法
- getState() //获取当前的数据
- dispatch(action) //往reducer 提交一个action
- 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
- 截获action
- 发出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
- 单页应用需要进行页面切换
- 通过URL可以定位到页面
- 更有语义的组织资源,(实现资源隔离)
实现原理
- 路由定义,形成路径和资源加载(组件)的映射关系
- 通过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特性
- 声明式路由定义,(可以像html标签一样随意使用,比较直观)
- 动态路由,(在页面render的时候才会实时解析配置),
react-router实现方式
三种配置模式
- URL路径,(通过url路径匹配,直接是斜杠路径变更) /router/home
- hash路由,改变hash值来实现路径匹配, /#/router/home, 可以支持低版本浏览器
- 内存路由, MemoryRouter, 路由变化不会体现在浏览器,而是在内存中
<HashRouter><HistoryRouter><MemoryRouter>
基于路由配置进行资源组织
- 实现业务逻辑的松耦合
- 易于扩展,重构和维护
- 路由层面实现lozy load
ReactRouter Api
- <Link> 普通连接,不会触发浏览器刷新
- <NavLink> 类似<Link> 但是会添加选中状态
- <Prompt> 满足条件时 提示用户是否离开当前页面
- <Ridirect> 重定向当前页面 例如登录判断
- <Route> 路由配置的核心标记,路径匹配时显示对应组件
- <Switch> 只显示第一个匹配的路由
通过url传递参数
-
通过url传递参数
<Route path='/topic/:id' > -
获取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 -> 静态资源存放位置
路由构建
-
使用"next/link" 定义链接
-
点击链接时页面不会刷新
-
使用prefetch 预加载目标资源
<Link prefetch href='/about' > 使用该属性 会预加载当前路由 对应的组件 -
使用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过程
- 得到实例化 App 对象 instance
- renderedElement = instance.render();
- 初始化 renderedElement 得到 child
- child.mountComponent(container)
- 在第一步得到 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 执行合成事件。
- 释放处理完成的事件。