17网站一起做网店怎么下单,创办个人网站,安庆集团网站建设,对网站提出的优化建议① 初探 HMTL的渲染过程 这个结构化文本就是 HTML 文本#xff0c; HTML 中的每个元素都对应 DOM中某个节点#xff0c;这样#xff0c;因为 HTML 元素的逐级包含关系#xff0c; DOM 节点自然就构成了一个树形结构#xff0c;称为 DOM 树 。 浏览器为了渲染 HTML 格…① 初探 HMTL的渲染过程 这个结构化文本就是 HTML 文本 HTML 中的每个元素都对应 DOM中某个节点这样因为 HTML 元素的逐级包含关系 DOM 节点自然就构成了一个树形结构称为 DOM 树 。 浏览器为了渲染 HTML 格式的网页会先将 HTML 文本解析以构建 DOM 树然后根据 DOM 树渲染出用户看到的界面当要改变界面内容的时候就去改变 DOM 树上的节点 。 纯函数 React 的理念 归结为一个公式就像下面这样 UIrender(data) 让我们来看看这个公式表达的含义用户看到的界面 UI)应该是一个函数在这里叫 render的执行结果只接受数据 data作为参数 。 这个函数是一个纯函数所谓纯函数指的是没有任何副作用输出完全依赖于输入的函数两次函数调用如果输人 相同得到的结果也绝对相同 。 如此一来最终的用户界面在 render 函数确定的情况下完全取决于输入数据 。 React初解 react的功能其实很单一主要负责渲染的功能现有的框架比如angular是一个大而全的框架用了angular几乎就不需要用其他工具辅助配合. PS: react感觉类似VScode或sublime需要装各种插件来写代码,而angular就像webstorm一样集成了很多功能 React 是什么 用脚本进行DOM操作的代价很昂贵。把DOM和JavaScript各自想象为一个岛屿它们之间用收费桥梁连接js每次访问DOM都要途径这座桥并交纳“过桥费”,访问DOM的次数越多费用也就越高。 因此推荐的做法是尽量减少过桥的次数努力待在ECMAScript岛上。因为这个原因react的虚拟dom就显得难能可贵了它创造了虚拟dom并且将它们储存起来每当状态发生变化的时候就会创造新的虚拟节点和以前的进行对比让变化的部分进行渲染。 整个过程没有对dom进行获取和操作只有一个渲染的过程所以react说是一个ui框架。 组件的生命周期 组件在初始化时会触发5个钩子函数 1、getDefaultProps() 设置默认的props也可以用defaultProps设置组件的默认属性。 这个函数只在 React.createClass 方法创造的组件类才会用到 。 2、getInitialState() 在使用es6的class语法时是没有这个钩子函数的可以直接在constructor中定义this.state。此时可以访问this.props。 这个函数只在 React.createClass 方法创造的组件类才会用到 。 3、componentWillMount() 组件初始化时只调用以后组件更新不调用整个生命周期只调用一次此时可以修改state。 4、 render() react最重要的步骤创建虚拟dom进行diff算法更新dom树都在此进行。此时就不能更改state了。 通常一个组件要发挥作用总是要渲染一些东西 render 函数并不做实际的谊染动作它只是返回一个 JSX 描述的结构最终由 React 来操作渲染过程。 当然某些特殊组件的作用不是渲染界面或者组件在某些情况下选择没有东西可画那就让 render 函数返回一个 null 或者 false 等于告诉 React这个组件这次不需要渲染任何 DOM 元素 。 需要注意 render 函数应该是一个纯函数完全根据 this.state 和 this.props 来决定返回的结果而且不要产生任何副作用。在 render 函数中去调用 this.setState 毫无疑问是错误的因为一个纯函数不应该引起状态的改变。 5、componentDidMount() Render 函数返回的东西已 经引发了渲染组件已经被“装载”到了 DOM 树上 。 组件渲染之后调用可以通过this.getDOMNode()获取和操作dom节点只调用一次。 在更新时也会触发5个钩子函数 6、componentWillReceivePorps(nextProps) 组件初始化时不调用组件接受新的props时调用。 7、shouldComponentUpdate(nextProps, nextState) React性能优化非常重要的一环。组件接受新的state或者props时调用我们可以设置在此对比前后两个props和state是否相同如果相同则返回false阻止更新因为相同的属性状态一定会生成相同的dom树这样就不需要创造新的dom树和旧的dom树进行diff算法对比节省大量性能尤其是在dom结构复杂的时候。不过调用this.forceUpdate会跳过此步骤。 8、componentWillUpdate(nextProps, nextState) 组件初始化时不调用只有在组件将要更新时才调用此时可以修改state 9、render() 当组件的state或者props发生改变的时候,render函数就会重新执行 10、componentDidUpdate() 组件初始化时不调用组件更新完成后调用此时可以获取dom节点。 还有一个卸载钩子函数 11、componentWillUnmount() 组件将要卸载时调用一些事件监听和定时器需要在此时清除。 以上可以看出来react总共有10个周期函数render重复一次这个10个函数可以满足我们所有对组件操作的需求利用的好可以提高开发效率和组件性能。 render 和 shouldComponentUpdate函数也是 React 生命周期函数中唯二两个要求有返回结果的函数。 render 函数的返回结果将用于构造 DOM 对象而 shouldComponentUpdate函数返回一个布尔值告诉 React 库这个组件在这次更新过程中是否要继续 。 V16 生命周期函数用法建议 class ExampleComponent extends React.Component {// 用于初始化 stateconstructor() {}// 用于替换 componentWillReceiveProps 该函数会在初始化和 update 时被调用// 因为该函数是静态函数所以取不到 this// 如果需要对比 prevProps 需要单独在 state 中维护static getDerivedStateFromProps(nextProps, prevState) {}// 判断是否需要更新组件多用于组件性能优化shouldComponentUpdate(nextProps, nextState) {}// 组件挂载后调用// 可以在该函数中进行请求或者订阅componentDidMount() {}// 用于获得最新的 DOM 数据getSnapshotBeforeUpdate() {}// 组件即将销毁// 可以在此处移除订阅定时器等等componentWillUnmount() {}// 组件销毁后调用componentDidUnMount() {}// 组件更新后调用componentDidUpdate() {}// 渲染组件函数render() {}// 以下函数不建议使用UNSAFE_componentWillMount() {}UNSAFE_componentWillUpdate(nextProps, nextState) {}UNSAFE_componentWillReceiveProps(nextProps) {}
}
复制代码父子组件的渲染过程 因为 render 函数本身并不往 DOM 树上渲染或者装载内容它只是返回一个 JSX 表示的对象然后由 React 库来根据返回对象决定如何渲染 。而 React 库肯定是要把所有组件返回的结果综合起来才能知道该如何产生对应的 DOM修改 。 所以只有 React 库调用三个 Counter 组件的 render 函数之后才有可能完成装载这时候才会依次调用各个组件的 componentDidMount 函数作为装载过程的收尾 。 React的组件化 react的一个组件很明显的由dom视图和state数据组成两个部分泾渭分明。 state是数据中心它的状态决定着视图的状态。这时候发现似乎和我们一直推崇的MVC开发模式有点区别没了Controller控制器那用户交互怎么处理数据变化谁来管理 然而这并不是react所要关心的事情它只负责ui的渲染。与其他框架监听数据动态改变dom不同react采用setState来控制视图的更新。 setState会自动调用render函数触发视图的重新渲染如果仅仅只是state数据的变化而没有调用setState并不会触发更新。 组件就是拥有独立功能的视图模块许多小的组件组成一个大的组件整个页面就是由一个个组件组合而成。它的好处是利于重复利用和维护。 UI render(data) React 组件扮 演的是 render 函数的角色应该是一个没有副作用的纯函数。修改 props 的值 是一个副作用组件应该避免。 组件类别 概念: 所谓组件简单说指的是能完成某个特定功能的独立的 、 可重用的代码 。 容器组件 只关心逻辑,不负责页面渲染UI组件 不关心逻辑,只负责页面渲染无状态组件 没有render()函数,只是一个函数,没有声明周期函数,效率更高 React的 Diff算法 当组件更新的时候react会创建一个新的虚拟dom树并且会和之前储存的dom树进行比较这个比较多过程就用到了diff算法所以组件初始化的时候是用不到的。 react提出了一种假设相同的节点具有类似的结构而不同的节点具有不同的结构。在这种假设之上进行逐层的比较如果发现对应的节点是不同的那就直接删除旧的节点以及它所包含的所有子节点然后替换成新的节点。如果是相同的节点则只进行属性的更改。 对于列表的diff算法稍有不同因为列表通常具有相同的结构在对列表节点进行删除插入排序的时候单个节点的整体操作远比一个个对比一个个替换要好得多所以在创建列表的时候需要设置key值这样react才能分清谁是谁。当然不写key值也可以但这样通常会报出警告通知我们加上key值以提高react的性能。 演变过程: JSX createElement 虚拟dom (JS对象) 真实dom 虚拟Dom的对比算法 不同类型的元素 每当根元素有不同类型React将卸载旧树并重新构建新树。从a到img或从Article到Comment或从Button 到 div任何的调整都会导致全部重建。 当树被卸载旧的DOM节点将被销毁。组件实例会调用componentWillUnmount()。当构建一棵新树新的DOM节点被插入到DOM中。组件实例将依次调用componentWillMount()和componentDidMount()。任何与旧树有关的状态都将丢弃。 这个根节点下所有的组件都将会被卸载同时他们的状态将被销毁。 相同类型的DOM元素 当比较两个相同类型的React DOM元素时React则会观察二者的属性保持相同的底层DOM节点并仅更新变化的属性。 相同类型的组件元素 当组件更新时实例仍保持一致以让状态能够在渲染之间保留。React通过更新底层组件实例的props来产生新元素并在底层实例上依次调用componentWillReceiveProps() 和 componentWillUpdate() 方法。 接下来render()方法被调用同时对比算法会递归处理之前的结果和新的结果。 React diff算法流程图 key的作用 React DOM 首先会比较元素内容先后的不同而在渲染过程中只会更新改变了的部分。 key的重要性: 提高对比的效率 Keys可以在DOM中的某些元素被增加或删除的时候帮助React识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。 用数组下标作为 key看起来 key 值是唯一的但是却不是稳定不变的随着 todos数组值的不同同样一个 Todoltem 实例在不同的更新过程中在数组中的下标完全可能不同把下标当做 key 就让 React 彻底乱套了 。 需要注意虽然 key 是一个 prop 但是接受 key 的组件并不能读取到 key 的值因为 key 和 ref 是 React 保留的两个特殊 prop 并没有预期让组件直接访问 。 为什么使用setState修改数据? 直接修改this.state的值虽然事实上改变了组件的内部状态但只是野蛮地修改了state 却没有驱动组件进行重新渲染既然组件没有重新渲染当然不会反应 this.state值的变化 而 this.setState函数所做的事情首先是改变 this.state 的值然后驱动组件经历更新过程这样才有机会让 this.state 里新的值出现在界面上 。 setState 是异步函数? setState() 排队更改组件的 state 并通过更新 state来告诉 React该组件及其子组件需要重新渲染。这是用于 响应事件处理程序 和 服务器响应 更新用户界面的主要方法。 记住 setState() 作为一个请求而不是立即命令来更新组件。为了更好的感知性能React 可能会延迟它然后合并多个setState()更新多个组件。React不保证state 更新就立即应用(重新渲染)。 React 可以将多个setState() 调用合并成一个调用来提高性能。 因为 this.props 和 this.state 可能是异步更新的你不应该依靠它们的值来计算下一个状态。setState() 并不总是立即更新组件。它可能会 批量 或 延迟到后面更新。这使得在调用 setState() 之后立即读取 this.state 存在一个潜在的陷阱。 而使用 componentDidUpdate 或 setState 回调setState(updater, callback)在应用更新后都将被保证触发。 举个例子: 例如此代码可能无法更新计数器 // Wrong
this.setState({counter: this.state.counter this.props.increment,
});
复制代码 要修复它请使用第二种形式的 setState() 来接受一个函数而不是一个对象。 该函数将接收先前的状态作为第一个参数将此次更新被应用时的props做为第二个参数 // Correct
this.setState((prevState, props) ({counter: prevState.counter props.increment
}));
复制代码 setState()总是会导致重新渲染除非 shouldComponentUpdate()返回 false 。如果可变对象被使用并且条件渲染逻辑不能在shouldComponentUpdate() 中实现只有当新state与先前 state 不同时调用 setState()才能避免不必要的重新渲染。 不能在render()里面写this.setState()会导致循环修改 React组件写法 ES6的class类可以看作是构造函数的一个语法糖可以把它当成构造函数来看extends实现了类之间的继承 —— 定义一个类Main 继承React.Component所有的属性和方法组件的生命周期函数就是从这来的。 constructor是构造器在实例化对象时调用super调用了父类的constructor创造了父类的实例对象this然后用子类的构造函数进行修改。 super(props) 如果在构造函数中没有调用super(props),那么组件实例被构造之后类实例的所有成员函数就无法通过 this.props 访问到父组件传递过来的 props 值。很明显给 this.props 赋值是 React.Component 构造函数的工作之一 。 shouldCompnentUpdate生命周期 在通用的 shouldCompnentUpdate 函数中做“浅层比较”是一个被普遍接受的做法如果需要做“深层比较”那就是某个特定组件的行为需要开发者自己根据组件情况去编写 。 PureComponent React15.3 中新加了一个类PureComponent前身是 PureRenderMixin 和 Component 基本一样只不过会在 render之前帮组件自动执行一次shallowEqual浅比较来决定是否更新组件浅比较类似于浅复制只会比较第一层。使用 PureComponent 相当于省去了写 shouldComponentUpdate 函数当组件更新时如果组件的 props 和 state 引用和第一层数据都没发生改变 render 方法就不会触发这是我们需要达到的效果。虽然第一层数据没变但引用变了就会造成虚拟 DOM 计算的浪费。第一层数据改变但引用没变会造成不渲染所以需要很小心的操作数据。so. 为了性能,React只做了浅对比,于是就有了immutable.js immutable.js 高阶组件 高阶组件就是一个函数且该函数接受一个组件作为参数并返回一个新的组件 const EnhancedComponent higherOrderComponent(WrappedComponent);
复制代码 对比组件将props属性转变成UI高阶组件则是将一个组件转换成另一个新组件。 高阶组件在React第三方库中很常见比如Redux的connect方法和Relay的createContainer. Refs属性 创建 Refs 使用 React.createRef() 创建 refs通过 ref 属性来获得 React 元素。当构造组件时refs 通常被赋值给实例的一个属性这样你可以在组件中任意一处使用它们. class MyComponent extends React.Component {constructor(props) {super(props);this.myRef React.createRef();}render() {return div ref{this.myRef} /;}
}
复制代码ref的值取决于节点的类型: 当 ref 属性被用于一个普通的 HTML 元素时React.createRef() 将接收底层 DOM 元素作为它的 current 属性以创建 ref 。当 ref 属性被用于一个自定义类组件时ref 对象将接收该组件已挂载的实例作为它的 current 。你不能在函数式组件上使用 ref 属性因为它们没有实例。React-Router路由 Router就是React的一个组件它并不会被渲染只是一个创建内部路由规则的配置对象根据匹配的路由地址展现相应的组件。 Route则对路由地址和组件进行绑定Route具有嵌套功能表示路由地址的包涵关系这和组件之间的嵌套并没有直接联系。Route可以向绑定的组件传递7个属性childrenhistorylocationparamsrouterouteParamsroutes每个属性都包涵路由的相关的信息。 比较常用的有children以路由的包涵关系为区分的组件location包括地址参数地址切换方式key值hash值。 react-router提供Link标签这只是对a标签的封装值得注意的是点击链接进行的跳转并不是默认的方式react-router阻止了a标签的默认行为并用pushState进行hash值的转变。 切换页面的过程是在点击Link标签或者后退前进按钮时会先发生url地址的转变Router监听到地址的改变根据Route的path属性匹配到对应的组件将state值改成对应的组件并调用setState触发render函数重新渲染dom。 路由(按需加载) 当页面比较多时项目就会变得越来越大尤其对于单页面应用来说初次渲染的速度就会很慢这时候就需要按需加载只有切换到页面的时候才去加载对应的js文件。react配合webpack进行按需加载的方法很简单Route的component改为getComponent组件用require.ensure的方式获取并在webpack中配置chunkFilename。 const chooseProducts (location, cb) {require.ensure([], require {cb(null, require(../Component/chooseProducts).default)},chooseProducts)
}const helpCenter (location, cb) {require.ensure([], require {cb(null, require(../Component/helpCenter).default)},helpCenter)
}const saleRecord (location, cb) {require.ensure([], require {cb(null, require(../Component/saleRecord).default)},saleRecord)
}const RouteConfig (Router history{history}Route path/ component{Roots}IndexRoute component{index} ///首页Route pathindex component{index} /Route pathhelpCenter getComponent{helpCenter} ///帮助中心Route pathsaleRecord getComponent{saleRecord} ///销售记录Redirect from* to/ //Route/Router
);
复制代码组件之间的通信 react推崇的是单向数据流通常被称为自顶向下或单向数据流。 任何状态始终由某些特定组件所有并且从该状态导出的任何数据或 UI 只能影响树中下方的组件。 解决通信问题的方法很多: 如果只是父子级关系父级可以将一个回调函数当作属性传递给子级子级可以直接调用函数从而和父级通信。组件层级嵌套到比较深可以使用上下文getChildContext来传递信息这样在不需要将函数一层层往下传任何一层的子级都可以通过this.context直接访问。兄弟关系的组件之间无法直接通信它们只能利用同一层的上级作为中转站。而如果兄弟组件都是最高层的组件为了能够让它们进行通信必须在它们外层再套一层组件这个外层的组件起着保存数据传递信息的作用这其实就是redux所做的事情。组件之间的信息还可以通过全局事件来传递。不同页面可以通过参数传递数据下个页面可以用location.param来获取。React的事件委托 我们在 JSX 中看到一个组件使用了 onClick但并没有产生直接使用 onclick 注意是 onclick 不是 onClick的HTML 而是使用了事件委托event delegation的方式处理点击事件无论有多少个 onClick 出现其实最后都只在 DOM 树上添加了一个事件处理函数挂在最顶层的 DOM 节点上。 所有的点击事件都被这个事件处理函数捕获然后根据具体组件分配给特定函数使用事件委托的性能当然要比为每个 onClick 都挂载一个事件处理函数要高 。 因为 React 控制了组件的生命周期在 unmount 的时候自然能够清除相关的所有事 件处理函数内存泄露也不再是一个问题。 ② 进阶 Redux 基本原则 Flux 的基本原则是“单向数据流” Redux 在此基础上强调三个基本原则 唯一数据源 Single Source of Truth); 在 Flux 中应用可以拥有多个 Store 往往根据功能把应用的状态 数据划分给若干个 Store 分别存储管理 。 Redux 对这个问题的解决方法就是整个应用只保持一个 Store 所有组件的数据源 就是这个 Store 上的状态 。 保持状态只读 State is read-only); 保持状态只读就是说不能去直接修改状态要修改 Store 的状态必须要通过派发 一个 action 对象完成这一点 和 Flux 的要求并没有什么区别 。 当然要驱动用户界面渲染就要改变应用的状态但是改变状态的方法不是去修 改状态上值而是创建一个新的状态对象返回给 Redux 由 Redux 完成新的状态的组装 。 数据改变只能通过纯函数完成 Changes are made with pure functions 。 在 Redux 中 每个 reducer 的函数签名如下所示 reducer(state , action ) 第一个参数 state 是当前的状态第二个参数 action 是接收到的 action 对象而 reducer函数要做的事情就是根据 state 和 action 的值产生一个新的对象返回注意 reducer 必须是纯函数也就是说函数的返回结果必须完全由参数 state 和 action 决定而且不产生任何副作用也不能修改参数 state 和 action 对象。 Redux核心API Redux主要由三部分组成storereduceraction。 store Redux的核心是store它由Redux提供的 createStore(reducer defaultState)这个方法生成生成三个方法getState(),dispatch(),subscrible()。 getState()存储的数据状态树dispatch(action)分发action并返回一个action这是唯一能改变store中数据的方式subscrible(listener)注册一个监听者store发生变化的时候被调用。reducer reducer是一个纯函数它根据previousState和action计算出新的state。 reducer(previousState,action) action action本质上是一个JavaScript对象其中必须包含一个type字段来表示将要执行的动作其他的字段都可以根据需求来自定义。 const ADD_TODO ADD_TODO
复制代码{type: ADD_TODO,text: Build my first Redux app
}
复制代码整合 他们三者之间的交互可以由下图概括 概念分析: redux主要由三部分组成storereduceraction。 store是一个对象它有四个主要的方法 1、dispatch: 用于action的分发——在createStore中可以用middleware中间件对dispatch进行改造比如当action传入dispatch会立即触发reducer有些时候我们不希望它立即触发而是等待异步操作完成之后再触发这时候用redux-thunk对dispatch进行改造以前只能传入一个对象改造完成后可以传入一个函数在这个函数里我们手动dispatch一个action对象这个过程是可控的就实现了异步。 2、subscribe 监听state的变化——这个函数在store调用dispatch时会注册一个listener监听state变化当我们需要知道state是否变化时可以调用它返回一个函数调用这个返回的函数可以注销监听。 let unsubscribe store.subscribe(() {console.log(state发生了变化)}) 3、getState 获取store中的state——当我们用action触发reducer改变了state时需要再拿到新的state里的数据毕竟数据才是我们想要的。 getState主要在两个地方需要用到一是在dispatch拿到action后store需要用它来获取state里的数据并把这个数据传给reducer这个过程是自动执行的二是在我们利用subscribe监听到state发生变化后调用它来获取新的state数据如果做到这一步说明我们已经成功了。 4、replaceReducer: 替换reducer改变state修改的逻辑。 store可以通过createStore()方法创建接受三个参数经过combineReducers合并的reducer和state的初始状态以及改变dispatch的中间件后两个参数并不是必须的。store的主要作用是将action和reducer联系起来并改变state。 action: action是一个对象其中type属性是必须的同时可以传入一些数据。action可以用actionCreactor进行创造。dispatch就是把action对象发送出去。 reducer: reducer是一个函数它接受一个state和一个action根据action的type返回一个新的state。根据业务逻辑可以分为很多个reducer然后通过combineReducers将它们合并state树中有很多对象每个state对象对应一个reducerstate对象的名字可以在合并时定义。 const reducer combineReducers({a: doSomethingWithA,b: processB,c: c
})
复制代码combineReducers: 其实它也是一个reducer它接受整个state和一个action然后将整个state拆分发送给对应的reducer进行处理所有的reducer会收到相同的action不过它们会根据action的type进行判断有这个type就进行处理然后返回新的state没有就返回默认值然后这些分散的state又会整合在一起返回一个新的state树。 流程分析: 首先调用store.dispatch将action作为参数传入同时用getState获取当前的状态树state并注册subscribe的listener监听state变化再调用combineReducers并将获取的state和action传入。combineReducers会将传入的state和action传给所有reducer并根据action的type返回新的state触发state树的更新我们调用subscribe监听到state发生变化后用getState获取新的state数据。redux的state和react的state两者完全没有关系除了名字一样。 React-Redux React-redux是怎么配合的 react-redux 的两个最主要功能 connect 连接容器组件和视图组件Provider 提供包含 store 的 context。react-redux提供了connect和Provider两个好基友它们一个将组件与redux关联起来一个将store传给组件。组件通过dispatch发出actionstore根据action的type属性调用对应的reducer并传入state和这个actionreducer对state进行处理并返回一个新的state放入storeconnect监听到store发生变化调用setState更新组件此时组件的props也就跟着变化。值得注意的是connectProvidermapStateToProps,mapDispatchToProps是react-redux提供的redux本身和react没有半毛钱关系它只是数据处理中心没有和react产生任何耦合是react-redux让它们联系在一起。Redux 本身和React没有关系只是数据处理中心是React-Redux让他们联系在一起。 React-Redux的两个方法 connect 掘金资料 connect连接React组件和Redux store。connect实际上是一个高阶函数返回一个新的已与 Redux store 连接的组件类。 const VisibleTodoList connect(mapStateToProps,mapDispatchToProps
)(TodoList)
复制代码TodoList是 UI 组件VisibleTodoList就是由 react-redux 通过connect方法自动生成的容器组件。 mapStateToProps从Redux状态树中提取需要的部分作为props传递给当前的组件。mapDispatchToProps将需要绑定的响应事件action作为props传递到组件上。**书籍资料 ** export default connect(mapStateToProps, mapDispatchToProps) ( Counter);
复制代码这个 connect 函数具体做了什么工作呢 作为容器组件要做的工作无外乎两件事 把 Store 上的状态转化为内层傻瓜组件的 prop;把内层傻瓜组件中的用户动作转化为派送给 Store 的动作 。 Provider Provider实现store的全局访问将store传给每个组件。 原理使用React的contextcontext可以实现跨组件之间的传递。 如果只使用redux那么流程是这样的 component -- dispatch(action) -- reducer -- subscribe -- getState -- component 用了react-redux之后流程是这样的 component -- actionCreator(data) -- reducer -- component store的三大功能dispatchsubscribegetState都不需要手动来写了。 react-redux帮我们做了这些同时它提供了两个好基友Provider和connect。 Provider是一个组件它接受store作为props然后通过context往下传这样react中任何组件都可以通过context获取store。 也就意味着我们可以在任何一个组件里利用dispatch(action)来触发reducer改变state并用subscribe监听state的变化然后用getState获取变化后的值。但是并不推荐这样做它会让数据流变的混乱过度的耦合也会影响组件的复用维护起来也更麻烦。 connect --connect(mapStateToProps, mapDispatchToProps, mergeProps, options) 是一个函数它接受四个参数并且再返回一个函数--wrapWithConnectwrapWithConnect接受一个组件作为参数wrapWithConnect(component)它内部定义一个新组件Connect(容器组件)并将传入的组件(ui组件)作为Connect的子组件然后return出去。 所以它的完整写法是这样的connect(mapStateToProps, mapDispatchToProps, mergeProps, options)(component) mapStateToProps(state, [ownProps]) mapStateToProps 接受两个参数store的state和自定义的props并返回一个新的对象这个对象会作为props的一部分传入ui组件。我们可以根据组件所需要的数据自定义返回一个对象。ownProps的变化也会触发mapStateToProps function mapStateToProps(state) {return { todos: state.todos };
}
复制代码mapDispatchToProps(dispatch, [ownProps]) mapDispatchToProps如果是对象那么会和store绑定作为props的一部分传入ui组件。 如果是个函数它接受两个参数bindActionCreators会将action和dispatch绑定并返回一个对象这个对象会和ownProps一起作为props的一部分传入ui组件。 所以不论mapDispatchToProps是对象还是函数它最终都会返回一个对象如果是函数这个对象的key值是可以自定义的 function mapDispatchToProps(dispatch) {return {todoActions: bindActionCreators(todoActionCreators, dispatch),counterActions: bindActionCreators(counterActionCreators, dispatch)};
}
复制代码mapDispatchToProps返回的对象其属性其实就是一个个actionCreator因为已经和dispatch绑定所以当调用actionCreator时会立即发送action而不用手动dispatch。ownProps的变化也会触发mapDispatchToProps。 mergeProps(stateProps, dispatchProps, ownProps) 将mapStateToProps() 与 mapDispatchToProps()返回的对象和组件自身的props合并成新的props并传入组件。默认返回 Object.assign({}, ownProps, stateProps, dispatchProps) 的结果。 options pure true 表示Connect容器组件将在shouldComponentUpdate中对store的state和ownProps进行浅对比判断是否发生变化优化性能。为false则不对比。 其实connect函数并没有做什么大部分的逻辑都是在它返回的wrapWithConnect函数内实现的确切的说是在wrapWithConnect内定义的Connect组件里实现的。 在项目中我使用的大store目录结构是: // index.js
import {createStore, compose, applyMiddleware} from redux;
import thunk from redux-thunk;
import reducers from ./reducers;const composeEnhancers window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store createStore(reducers, composeEnhancers(applyMiddleware(thunk)
));export default store;
复制代码// reducers.js
// 合并小的reducer
import {combineReducers} from redux-immutable; // 提供的是immutable数据
import {reducer as userReducer} from ./user
import {reducer as chatUserReducer} from ./chat_user
import {reducer as chatReducer} from ./chatconst reducer combineReducers({user: userReducer,chatUser: chatUserReducer,chat: chatReducer
});export default reducer;
复制代码在项目中我使用的小store(举例)目录结构是: 核心代码: // _reducer.js
import * as constants from ./constants
import {getRedirectPath} from ../../common/js/utilconst initState {isAuth: false,msg: ,user: ,pwd: ,type:
}const defaultState (localStorage.getItem(jobUser) JSON.parse(localStorage.getItem(jobUser))) || initStateexport default (state defaultState, action) {switch (action.type) {case constants.AUTH_SUCCESS:localStorage.setItem(jobUser, JSON.stringify({...state,msg: ,redirectTo: getRedirectPath(action.payload), ...action.payload}))return {...state, msg: , redirectTo: getRedirectPath(action.payload), ...action.payload}case constants.LOAD_DATA:return {...state, ...action.payload}case constants.ERROR_MSG:return {...state, isAuth: false, msg: action.msg}case constants.LOGIN_OUT:return {redirectTo: /login, ...initState}default:return state}
}
复制代码// actionCreators.js
import * as constants from ./constants
import axios from axios
const authSuccess (obj) {const {pwd, ...data} objreturn {type: constants.AUTH_SUCCESS, payload: data}
}
const errorMsg (msg) {return {msg, type: constants.ERROR_MSG}
}// 注册
export function register({user, pwd, repeatpwd, type}) {if (!user || !pwd || !type) {return errorMsg(用户名密码必须输入)}if (pwd ! repeatpwd) {return errorMsg(密码和确认密码不同)}return dispatch {axios.post(/user/register, {user, pwd, type}).then(res {if (res.status 200 res.data.code 0) {dispatch(authSuccess(res.data.data))} else {dispatch(errorMsg(res.data.msg))}})}
}// 登录
export function login({user, pwd}) {if (!user || !pwd) {return errorMsg(用户名密码必须输入)}return dispatch {axios.post(/user/login, {user, pwd}).then(res {if (res.status 200 res.data.code 0) {dispatch(authSuccess(res.data.data))} else {dispatch(errorMsg(res.data.msg))}})}
}// 登出
export function logoutSubmit() {return {type: constants.LOGIN_OUT}
}// 修改
export function update(data) {return dispatch {axios.post(/user/update, data).then(res {if (res.status 200 res.data.code 0) {dispatch(authSuccess(res.data.data[0]))} else {dispatch(errorMsg(res.data.msg))}})}
}
复制代码// constants.js
export const AUTH_SUCCESS AUTH_SUCCESS
export const LOGIN_OUT LOGIN_OUT
export const ERROR_MSG ERROR_MSG
export const LOAD_DATA LOAD_DATA
复制代码// index.js
import reducer from ./_reducer
import * as actionCreators from ./actionCreators
import * as constants from ./constantsexport {reducer, actionCreators, constants}
复制代码完整的 react -- redux -- react 流程 一、Provider组件接受redux的store作为props然后通过context往下传。 二、 connect函数在初始化的时候会将mapDispatchToProps对象绑定到store 如果mapDispatchToProps是函数则在Connect组件获得store后根据传入的store.dispatch和action通过bindActionCreators进行绑定再将返回的对象绑定到storeconnect函数会返回一个wrapWithConnect函数同时wrapWithConnect会被调用且传入一个ui组件wrapWithConnect内部使用class Connect extends Component定义了一个Connect组件传入的ui组件就是Connect的子组件 然后Connect组件会通过context获得store并通过store.getState获得完整的state对象将state传入mapStateToProps返回stateProps对象、mapDispatchToProps对象或mapDispatchToProps函数会返回一个dispatchProps对象stateProps、dispatchProps以及Connect组件的props三者通过Object.assign()或者mergeProps合并为props传入ui组件。然后在ComponentDidMount中调用store.subscribe注册了一个回调函数handleChange监听state的变化。 三、 此时ui组件就可以在props中找到actionCreator当我们调用actionCreator时会自动调用dispatch在dispatch中会调用getState获取整个state同时注册一个listener监听state的变化store将获得的state和action传给combineReducerscombineReducers会将state依据state的key值分别传给子reducer并将action传给全部子reducerreducer会被依次执行进行action.type的判断如果有则返回一个新的state如果没有则返回默认。combineReducers再次将子reducer返回的单个state进行合并成一个新的完整的state。此时state发生了变化。dispatch在state返回新的值之后会调用所有注册的listener函数其中包括handleChange函数handleChange函数内部首先调用getState获取新的state值并对新旧两个state进行浅对比如果相同直接return如果不同则调用mapStateToProps获取stateProps并将新旧两个stateProps进行浅对比如果相同直接return结束不进行后续操作。如果不相同则调用this.setState()触发Connect组件的更新传入ui组件触发ui组件的更新此时ui组件获得新的propsreact -- redux -- react 的一次流程结束。上面的有点复杂简化版的流程是 一、Provider组件接受redux的store作为props然后通过context往下传。 二、connect函数收到Provider传出的store然后接受三个参数mapStateToPropsmapDispatchToProps和组件并将state和actionCreator以props传入组件这时组件就可以调用actionCreator函数来触发reducer函数返回新的stateconnect监听到state变化调用setState更新组件并将新的state传入组件。 connect可以写的非常简洁mapStateToPropsmapDispatchToProps只不过是传入的回调函数connect函数在必要的时候会调用它们名字不是固定的甚至可以不写名字。 简化版本 connect(state state, action)(Component);
复制代码redux以及react-redux到底是怎么实现的? 总结 下图阐述了它们三者之间的工作流程 redux-thunk 中间件 代码示例: function createThunkMiddleware(extraArgument) {return ({ dispatch , getState }) next action {if (typeof action ’ function ’){return action(dispatch , getState , extraArgument);}return next(action);}
}
const thunk createThunkMiddleware();
export default thunk;
复制代码 我们看 redux-thunk 这一串函数中最里层的函数也就是实际处理每个 action 对象的函数。 首先检查参数 action 的类型如果是函数类型的话就执行这个 action 函数把dispatch 和 getState 作为参数传递进去否则就调用 next 让下一个中间件继续处理 action,这个处理过程和 redux-thunk 文档中描述的功能一致。 Redux的单向数据流是同步操作驱动 Redux 流程的 是 action 对象 每一个 action对象被派发到 Store 上之后同步地被分配给所有的 reducer 函数每个 reducer 都是纯函数纯函数不产生任何副作用自然是完成数据操作之后立刻同步返回 reducer 返回的结果又被同步地拿去更新 Store 上的状态数据更新状态数据的操作会立刻被同步给监听Store 状态改变的函数从而引发作为视图的 React 组件更新过程。 当我们想要让 Redux 帮忙处理一个异步操作的时候代码一样也要派发一个 action对象毕竟 Redux 单向数据流就是由 action 对象驱动的 。 但是这个引发异步操作的action 对象比较特殊我们叫它们“异步 action 对象” 。 前面例子中的 action 构造函数返回的都是一个普通的对象这个对象包含若干字段其中必不可少的字段是 type 但是“异步 action 对象”不是一个普通 JavaScript 对象而是一个函数 。 如果没有 redux-thunk 中间件的存在 这样一个函数类型的 action 对象被派发出来会一路发送到各个 reducer 函数 reducer 函数从这些实际上是函数的 action 对象上是无法获得 type 字段的所以也做不了什么实质的处理。 不过有了redux-thunk中间件之后这些 action 对象根本没有机会触及到 reducer函数在中间件一层就被 redux-thunk 截获 。 redux-thunk 的工作是检查 action 对象是不是函数如果不是函数就放行完成普通action 对象的生命周期而如果发现 action 对象是函数那就执行这个函数并把 Store的 dispatch 函数和 getState 函数作为参数传递到函数中去处理过程到此为止不会让这个异步 action 对象继续往前派发到 reducer 函数 。 React中间件机制 在 Redux框架中中间件处理的是 action 对象而派发 action 对象的就是 Store 上的dispatch 函数之前介绍过通过 dispatch 派发的 action 对象会进入 reducer 。 在 action 对象进入 reducer 之前会经历中间件的管道 。 在这个中间件管道中每个中间件都会接收到 action 对象在处理完毕之后就会把 action 对象交给下一个中间件来处理只有所有的中间件都处理完 action 对象之后在这个中间件管道中每个中间件都会接收到 action 对象在处理完毕之后就会把 action 对象交给下一个中间件来处理只有所有的中间件都处理完 action 对象之后才轮到 reducer 来处理 action 对象然而如果某个中间件觉得没有必要继续处理这个action 对象了就不会把 action 对象交给下一个中间件对这个 action 对象的处理就此中止也就轮不到 reducer 上场了 。 每个中间件必须要定义成一个函数返回一个接受 next 参数的函数而这个接受next 参数的函数又返回一个接受 action 参数的函数 。 next 参数本身也是一个函数中间件调用这个 next 函数通知 Redux 自己的处理工作已经结束 。 代码举例: // 一个实际上什么事都不做的中间件代码如下
function doNothingMiddleware{{dispatch, getState)) {return function {next) {return function {action) {return next{action)}}
}
复制代码 以 action 为参数的函数对传人的 action 对象进行处理因为 JavaScript 支持闭包 ( Clousure 在这个函数里可以访问上面两层函数的参数所以可以根据需要做很多事 情包括以下功能 调用 dispatch 派发出一个新 action 对象调用 getState 获得当前 Redux Store 上的状态调用 next 告诉 Redux 当前中间件工作完毕让 Redux 调用下一个中间件访问 action 对象 action 上的所有数据。 具有上面这些功能一个中间件足够获取 Store 上的所有信息也具有足够能力控制数据的流转 。中间件用于扩展 dispatch 函数的功能多个中间件实际构成了一个处理 action 对象的管道 action 对象被这个管道中所有中间件依次处理过之后才有机会被 reducer 处理。 ③ 起步 上面说了reactreact-router和redux的知识点。但是怎么样将它们整合起来搭建一个完整的项目。 1、先引用 react.jsreduxreact-router 等基本文件建议用npm安装直接在文件中引用。 2、从 react.jsreduxreact-router 中引入所需要的对象和方法。 import React, {Component, PropTypes} from react;
import ReactDOM, {render} from react-dom;
import {Provider, connect} from react-redux;
import {createStore, combineReducers, applyMiddleware} from redux;
import { Router, Route, Redirect, IndexRoute, browserHistory, hashHistory } from react-router;
复制代码3、根据需求创建顶层ui组件每个顶层ui组件对应一个页面。 4、创建actionCreators和reducers并用combineReducers将所有的reducer合并成一个大的reduer。利用createStore创建store并引入combineReducers和applyMiddleware。 5、利用connect将actionCreatorreuder和顶层的ui组件进行关联并返回一个新的组件。 6、利用connect返回的新的组件配合react-router进行路由的部署返回一个路由组件Router。 7、将Router放入最顶层组件Provider引入store作为Provider的属性。 8、调用render渲染Provider组件且放入页面的标签中。 可以看到顶层的ui组件其实被套了四层组件ProviderRouterRouteConnect这四个组件并不会在视图上改变react它们只是功能性的。 Github地址: wq93