内容简介:之前和大家分享了我们不需要系列:接下来我们继续调整难度, 替换应用范围更广的库:《我们或许不需要...》系列如果是做轮子就没有意义了, 此系列目的是通过简单的代码, 对原有库的设计思路进行概括提取, 最终从
之前和大家分享了我们不需要系列:
接下来我们继续调整难度, 替换应用范围更广的库: redux
, react-redux
《我们或许不需要...》系列如果是做轮子就没有意义了, 此系列目的是通过简单的代码, 对原有库的设计思路进行概括提取, 最终从 理解其理念 到 更高效 的开发项目的过程.
此行目的
Redux 在 React 中的重要性在此不再暂开, 其设计理念(单向数据流, 提供者模式)深得人心, 但是在实际开发中, 每个引用 Redux 的项目都会需要解决以下三个问题:
- 如何设计状态管理在工程中的模块结构
- 如何在不影响设计结构的前提下减少编写 action, reducer 的模板代码
- 如何减少不必要的重绘(immutable)
我们最后会基于 Context, 使用 20 行代码和一点点规范满足以上目标
本文中提到的代码都可以直接粘贴至项目中进行验证.
redux 官方最佳实践
首先编写项目入口
// src/index.js import React from 'react'; import { render } from 'react-dom'; import { Provider } from 'react-redux'; import Home from './Home'; import ChangerBar from './ChangerBar'; import store from './store'; // 我们在项目的最外层包裹一个 Provider 对象, 以实现提供者模式 function App() { return ( <Provider store={store}> <div> <ChangerBar /> <Home /> </div> </Provider> ); } render(<App />, document.getElementById('root')); 复制代码
接下来初始化状态, 并且编写 reducer, 根据后续 dispatch 传递的 action 对象修改状态
// src/store.js import { createStore } from 'redux'; const defaultStore = { name: 'dog', friends: ['cat', 'fish'], age: 100, }; function reducer(store = defaultStore, action) { switch (action.type) { case 'changeName': store = { ...store, name: action.name }; break; case 'addAge': store = { ...store, age: store.age + 1 }; break; default: break; } return store; } const store = createStore(reducer); store.dispatch({ type: 'init' }); export default store; 复制代码
接下来我们创建 actions 文件, 里面是 action 的集合
// src/actions.js export function changeName(name) { return { type: 'changeName', name, }; } export function addAge() { return { type: 'addAge', }; } 复制代码
我们绘制一个组件, 用来修改全局状态
react-redux 提供了一个 connect 组件, 它其实是一个 HOC(高阶组件), redux 这样的设计目的有两个:
- 监听和释放对 store 的订阅的行为被封装在 HOC 中, 这样可以不必每次都编写此逻辑代码;
- 将 state 和 disptch 对象转换为 props 注入至组件中, 而不是由组件去引用外部的对象, 这样组件内部只有一个概念就是 props.
// src/ChangerBar.js import React from 'react'; import { connect } from 'react-redux'; import * as actions from './actions'; const ChangerBar = ({ changeName, addAge }) => { function handleOnChange(e) { changeName(e.target.value); } return ( <div> <div>bar</div> <input placeholder="修改姓名" onChange={handleOnChange} /> <button type="button" onClick={addAge}> 更新年龄 </button> </div> ); }; // 将 dispatch 注入到组件Props中 function mapDispatchToProps(dispatch) { return { addAge: () => dispatch(actions.addAge()), changeName: name => dispatch(actions.changeName(name)), }; } export default connect( null, mapDispatchToProps, )(ChangerBar); 复制代码
实现 Home 组件, 订阅数据, 验证状态管理(UI 更新)
// src/Home.js import React from 'react'; import { connect } from 'react-redux'; const Home = ({ name, age }) => { return ( <div> <div>name: {name}</div> <div>age: {age}</div> </div> ); }; // 将 state 的值注入到 props 中 function mapStateToProps(state) { // 当任何一个 dispatch() 执行时, 此处将会重新运行, 并且注入新的 name 和 age 至组件 props 中, 以更新组件 return { name: state.name, age: state.age, }; } export default connect(mapStateToProps)(Home); 复制代码
以上代码相信有一定经验的 React 开发者已经非常熟悉, 当项目逐渐复杂时, 我们会逐步修改项目结构, 常见的有两种:
- 鸭子模式, 将状态管理分布在一个个页面中, 跨页面的状态管理提升至全局, dva 使用的是此模式, 每个需要使用全局状态的页面都会有一个自己的 action 和 reducer
- 中心化的状态管理: 将整个 actions 和 reducer 规整到一个全局目录中, actions 以事件为约定, 而不是以页面
这两种方式各有千秋, 鸭子模式的缺点是我们无法避免有跨页面的状态管理情况发生, 所以状态会被分布在全局和局部两处.
以上模式还有一个弊端就是 action.type 我们需要保持一致, 当 action 过多时, 可能需要一个 types 的文件用来存储 action.type 常量, 如此一来我们每编写一个新的状态需要:
- 打开 types 文件, 添加一个 action.type 常量
- 打开某个 action 文件, 引入 types, 编写 action
- 打开某个 reducer 文件, 引入 types, 编写 reducer
- 打开容器组件文件, 引入 connect, actions, 编写状态的获取和触发更新
以上还是没有引入 sage 和 immutable , 写到这里已经感受到我们 react 开发者正处于水深火热之中, 我们得加紧步伐.
接下来我们抛弃 redux 重写以上代码
利用 context 实现 react-redux 类似功能
redux 作者在 context API 更新之后, 提到过, 有了 context 我们可以不需要 redux 了, 所言非虚.
我们创建一个类似 createStore 的函数, 此函数会创建一个 Provider 和一个 store, 我们要利用不可变数据减少不必要的的重绘, 这里使用 immer:
// src/createContextRedux.js import React, { createContext, useMemo } from 'react'; import immer from 'immer'; export default function createContextRedux() { // 创建一个 context, 用于后续配合 useContext 进行更新组件 const store = createContext(); // 创建一个提供者组件 const Provider = ({ defaultState = {}, ...rest }) => { const [state, setState] = React.useState(defaultState); // 仅有 state 变更了, 才会重新更新 context 和 store return useMemo(() => { // 使用 immer 进行更新状态, 确保未更新的对象还是旧的引用 const dispatch = fn => setState(immer(state, v => fn(v))); store.state = state; store.dispatch = dispatch; return <store.Provider value={state} {...rest} />; }, [state]); }; return { Provider, store }; } 复制代码
好的, 这 20 行代码就是状态管理库的全部, 接下来我们利用它去实现刚刚的业务
重写 store, 我们引入刚刚编写的状态管理库, 然后创建全局 Provider 和 store:
// src/store.js import createContextRedux from './createContextRedux'; const { Provider, store } = createContextRedux(); export { Provider, store }; 复制代码
在项目最顶层使用 Provider 包裹, 以提供 context
// index.js import React from 'react'; import { render } from 'react-dom'; import { Provider } from './store'; import Home from './Home'; import ChangerBar from './ChangerBar'; function App() { return ( <Provider> <ChangerBar /> <Home /> </Provider> ); } render(<App />, document.getElementById('root')); 复制代码
这里我们移除了 reducer, 只有 action 的概念, 可以简化非常多的代码量
我修改 actions.js 文件, 由于当前只有 action 的概念, 所以非常适合使用 action 中心化的方式, 将容器组件的 action 都汇集放置一处, 容器组件仅读取状态和调用 action
// src/actions.js import { store } from './store'; export function changeName(name) { // 我们直接修改状态对象即可, 该函数会利用 immer 创建一个新的对象返回, 没有被修改的子对象还是旧的引用 store.dispatch(state => { state.name = name; }); } export function addAge() { store.dispatch(state => { if (state.age === void 0) { state.age = 0; } state.age += 1; }); } 复制代码
ChangerBar 不需要关联状态, 它只需要引用 actions 即可
// src/ChangerBar.js import React from 'react'; import * as actions from './actions'; const ChangerBar = () => { // 使用 hook 获取 context, 代替 conncet function handleOnChange(e) { actions.changeName(e.target.value); } function handleAddAage() { actions.addAge(); } return ( <div> <div>bar</div> <input placeholder="修改姓名" onChange={handleOnChange} /> <button type="button" onClick={handleAddAage}> 更新年龄 </button> </div> ); }; export default ChangerBar; 复制代码
最后在 Home 页面读取全局数据, 监听全局修改, 使用 useContext 代替 connect
// src/Home.js import React from 'react'; import { store } from './store'; const Home = () => { const { name, age } = React.useContext(store); return ( <div> <div>name: {name}</div> <div>age: {age}</div> </div> ); }; export default Home; 复制代码
序
有时候序也可以写在结尾, 不是么?
上面可能贴的代码太多了, 并且较为分散, 我们把它聚合成两个文件重新阅读:
实现状态管理
import React, { createContext, useMemo } from 'react'; import immer from 'immer'; export default function createContextRedux() { // 创建一个 context, 用于后续配合 useContext 进行更新组件 const store = createContext(); // 创建一个提供者组件 const Provider = ({ defaultState = {}, ...rest }) => { const [state, setState] = React.useState(defaultState); // 仅有 state 变更了, 才会重新更新 context 和 store return useMemo(() => { // 使用 immer 进行更新状态, 确保未更新的对象还是旧的引用 const dispatch = fn => setState(immer(state, v => fn(v))); store.state = state; store.dispatch = dispatch; return <store.Provider value={state} {...rest} />; }, [state]); }; return { Provider, store }; } 复制代码
使用状态管理
// src/index.js import React from 'react'; import { render } from 'react-dom'; import createContextRedux from './createStore'; const { Provider, store } = createContextRedux(); // 模拟一个异步 function fetchData() { return new Promise(res => { setTimeout(() => { res(); }, 500); }); } // 一个基础的 action, 用来修改状态 // 在实际项目中, action 应该统一放置一处, 不应该分散在各组件中 async function actionOfAddNum() { await fetchData(); store.dispatch(state => { state.age += 1; }); } // 点击之后, 利用 action 修改全局状态 function Changer() { return ( <button type="button" onClick={actionOfAddNum}> add </button> ); } // 利用 useContext 监听全局状态, 并随时进行更新 function Shower() { const { age } = React.useContext(store); return <div>age: {age}</div>; } function App() { return ( <Provider defaultState={{ age: 0 }}> <Shower /> <Changer /> </Provider> ); } render(<App />, document.getElementById('root')); 复制代码
最终我们移除了 redux, 在确保 单向数据流
的状态逻辑上, 移除了 reducer, connect 的步骤, 将模板代码减少至一份: 编写 action
;
通过分离 action 和组件, 解耦状态变更和更新.
谢谢观看
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Web前端黑客技术揭秘
钟晨鸣、徐少培 / 电子工业出版社 / 2013-1 / 59.00元
Web前端的黑客攻防技术是一门非常新颖且有趣的黑客技术,主要包含Web前端安全的跨站脚本(XSS)、跨站请求伪造(CSRF)、界面操作劫持这三大类,涉及的知识点涵盖信任与信任关系、Cookie安全、Flash安全、DOM渲染、字符集、跨域、原生态攻击、高级钓鱼、蠕虫思想等,这些都是研究前端安全的人必备的知识点。本书作者深入剖析了许多经典的攻防技巧,并给出了许多独到的安全见解。 本书适合前端工......一起来看看 《Web前端黑客技术揭秘》 这本书的介绍吧!