Giter VIP home page Giter VIP logo

blog's People

Contributors

amibug avatar

Watchers

 avatar  avatar  avatar

blog's Issues

react系列-bind this

问题


对于大多数前端开发来说,JavaScript 的 this 关键字会造成诸多困扰,由于 JavaScript 代码中的 this 指向并不清晰。在写react应用时,也会也到很多作用域绑定引起的问题,React组件ES6的写法,不会将方法内部的作用域自动绑定到组件的实例上。

下面展示一段问题代码

class Search extends Component {
	static propTypes = {
		onSearch: React.PropTypes.func.isRequired
	}
	onSearch() {
		console.log('表单值:', this.field.getValues());
		this.props.onSearch(this.field.getValues());
	}
	render(){
		const {init} = this.field;
		return <div>
			<Form direction="hoz" labelAlign="left">
					<FormItem label="loginID:">
						<Input placeholder="请输入loginID" {...init('loginID')}/>
					</FormItem>
					<Button type="primary" onClick={this.onSearch}>搜索</Button>
			</Form>
		</div>
	}
}

如果你真的尝试这么做了, 你会发现在onSearch中,因为this指向的是全局对象window而报错。

解决办法


我们都知道常规改变函数作用域的无非3种(Fiontion.prototype.bind call apply 三兄弟),下面讲解一下在es6中bind作用域的几种方式。

  1. 使用Function.prototype.bind()

    class Search extends Component {
    	render(){
    		return <div>
    			<Form direction="hoz" labelAlign="left">
    					<FormItem label="loginID:">
    						<Input placeholder="请输入loginID" {...init('loginID')}/>
    					</FormItem>
    					<Button type="primary" onClick={this.onSearch.bind(this)}>搜索</Button>
    			</Form>
    		</div>
    	}
    }
  2. ES7函数绑定语法
    在 ES7 中,有一个关于 bind 语法 的提议,提议将 :: 作为一个新的绑定操作符, 而且已经收录在stage-0提案中,实际上::是Function.propotype.bind()的一种语法糖。 幸运的是,Babel已经提供了对这个新语法的支持。

    class Search extends Component {
    	render(){
    		return <div>
    			<Form direction="hoz" labelAlign="left">
    					<FormItem label="loginID:">
    						<Input placeholder="请输入loginID" {...init('loginID')}/>
    					</FormItem>
    					<Button type="primary" onClick={::this.onSearch}>搜索</Button>
    			</Form>
    		</div>
    	}
    }
  3. 在构造函数中bind this

    	class Search extends Component {
    	constructor(props) {
    		super(props);
    		this.onSearch = this.onSearch.bind(this)
    	}
    	render(){
    		return <div>
    			<Form direction="hoz" labelAlign="left">
    					<FormItem label="loginID:">
    						<Input placeholder="请输入loginID" {...init('loginID')}/>
    					</FormItem>
    					<Button type="primary" onClick={this.onSearch}>搜索</Button>
    			</Form>
    		</div>
    	}
    }
  4. 使用箭头函数

    class Search extends Component {
    	render(){
    		return <div>
    			<Form direction="hoz" labelAlign="left">
    					<FormItem label="loginID:">
    						<Input placeholder="请输入loginID" {...init('loginID')}/>
    					</FormItem>
    					<Button type="primary" onClick={(...args)=>{
    						this.onSearch( ...args)
    					}}>搜索</Button>
    			</Form>
    		</div>
    	}
    }
  5. core-decorators.js
    core-decorators.js为开发者提供了一些实用的 decorator,其中实现的autobind修饰器能使得方法中的this对象绑定到原始对象

    class Search extends Component {
    	@autobind
    	onSearch() {
    		console.log('表单值:', this.field.getValues());
    		this.props.onSearch(this.field.getValues());
    	}
    	render(){
    		const {init} = this.field;
    		return <div>
    			<Form direction="hoz" labelAlign="left">
    					<FormItem label="loginID:">
    						<Input placeholder="请输入loginID" {...init('loginID')}/>
    					</FormItem>
    					<Button type="primary" onClick={this.onSearch}>搜索</Button>
    			</Form>
    		</div>
    	}
    }

总结


比较

这里我们讨论下以上几种将this绑定到react组件方案的缺点,优点自己体会吧。
方案1和方案2,缺点也很严重,这种方式破坏了组件的pure render,每次组件render时,子组件Button的onClick值都是重新赋值所得,会导致Button做一次无谓的render。而且函数绑定语法::属于es7草案中的特性,尚未纳入es标准。使用需要谨慎。
方案3和方案4会增加代码量
方案5需要引入第三方库,不过core-decorators.js提供了很多使用的装饰器。

场景

某些场景下,我们需要传递额外的参数,比如列表中删除操作,需要传id。常用的方案是方案1和方案4

// Function.prototype.bind()
<Item onClick={this.doDelete.bind(this, id)}>删除</Item>
// 箭头函数
<Item onClick={(...args)=>{
	this.doDelete(id, ...args)
}}>删除</Item>

简单的JavaScript组件化实现

最近看react例子,根据理解自己也简单实现了一下组件的继承和事件机制。

代码在这里

原始的组件写法

(function($) {
	$.pluginName = function(element, options) {
		var defaults = {
			title: '',
			content: '',
			showOKBtn: 1, // 显示确定按钮
			showCCBtn: 1, // 显示取消按钮
			onFoo: function() {} // callback
		}

		var plugin = this;
		plugin.settings = {}

		var $element = $(element);

		plugin.init = function(options) {
			this.settings = $.extend({}, defaults, options);
			this.initNode(options);
		}

		// public method.
		plugin.show = function() {
			// ...
		}
		plugin.hide = function() {
			// ...	
		}

		plugin.initNode = function(options) {
			var $okBtn = $element.find(''),
				$content = $element.find('');
			// ....
			// 部分逻辑
			$content.text(plugin.settings.content);
			$okBtn.on('click', $.proxy(this.onOk, this));
		}

		plugin.onOk = function(){
			this.hide();
			plugin.settings.onFoo();
		}

		plugin.init();
	}

	$.fn.pluginName = function(options) {
		return this.each(function() {
			if (undefined == $(this).data('pluginName')) {
				var plugin = new $.pluginName(this, options);
				$(this).data('pluginName', plugin);
			}
		});
	}
})(jQuery);

// 使用
var template = '<div>...弹框html...</div>';
$(template).pluginName({
	content: '确定删除该地址'
}).show();

一般我们写得入门级jquery组件,基本就是这样一个模板。
这里我们实现了一个基本的弹窗组件,也完成了需求方的要求,oh ye!enter image description here

某天需求mm说我们要加一个confirm信息的弹框,只要一个确定按钮!!还好还好。组件中本来就加了options.showCCBtn的配置,实例化的时候传一个参数就可以了。分分钟解决了mm的问题,还得到mm的赞许,想想都有点小激动呢。

处理一套风格相似的组件的时候,通过传递不同的参数来控制不同的ui显示和逻辑代码执行,确实可以解决问题,随着功能的一步步增加,这个组件就变得越来越臃肿,代码耦合成度变高,到最后自己都搞不清楚每个参数不同值代表的意思。况且在团队中都是多个人维护同一个组件,这简直就是一场悲剧。从此mm的态度也变得不好,你还坑害了维护组件的好基友。

继承

这时候面向对象的思维就出场了
enter image description here
我们发现设置titile, 关闭窗体是大家共有的功能。这里可以抽象成一个基础组件,新的组件继承这个组件即可。

(function() {
	var BaseWindon = Kclass.extend({
		init: function(options){
           //公共功能 
			$titleElm.text(options.title);
			$closeElm.on('click', $.proxy(this.close, this));
		},
                    // 销毁
        destroy: function(){
                    
		}
		close: function(){

		}
	};
	return BaseWindon;
})()

 在子类中 require('BaseWindon');

 (function() {
	var AddAddressWindon = BaseWindon.extend({
		init: function(options){
			// 调用parent的init
			this.supr();
		},
		validate: function(){

		},
		// 组件自己的功能
		submit: function(){

		}
	};
	return AddAddressWindon;
})()

javascript oo的实现有很多种,我用了ded/klass,supr的实现比较巧妙。主要原理为:获取方法的代码字符串,通过正则检测字符串中是否包含 supr,若包含, 则改写该方法,在改写的方法中动态的改变this.supr,使其指向父类同名方法,以完成调用父类方法的目的。具体的原理可看参考野生小技巧--继承中的super()实现

这时需求mm又来了,需求mm说当用户点击确定之后要加一个其他功能,其实第一个例子中,我们也可以实现这样的功能。我们可以传递一个callback onFoo。

我们需要引入一种更加优雅的方式,参考node的事件机制。大家知道,Node.js能够在众多的后端JavaScript技术之中脱颖而出,正是因其基于事件的特点而受到欢迎。

事件机制

事件机制对应着一种设计模式-观察者模式。

(function() {
	var win = new BaseWindow();

	win.on('ok', function(){
		console.log('on ok!');
	});

	win.emit('ok');  
	// log  --- on ok!
})()

实现在这里

显然事件驱动的方式更加优雅,相比之下第一种手动触发callback的方式显得有点out。事件机制的方式,在监听on和触发emit的时机上也显得更加灵活,对于只需要触发一次的callback,你只需要用once函数来监听。写代码好像突然变得好爽好舒服。

最后记得提供一个销毁组件的方法,一个简单的组件就完成了。
完整的代码在这里

当然要更好的组件还需要提供

  1. 模板机制
  2. 双向绑定

redux初见

Redux初见

本文记录的是自己对redux的学习和理解,希望可以简洁易懂,入门redux,一步步的走进redux!


Redux是什么

ReduxJavaScript应用的状态容器,提供可预测化的状态管理,让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。不仅于此,它还提供超爽的开发体验。

核心概念

  • store】保存应用状态全局单一
  • state】所有state以key-value的形式存储在store
  • action】描述发生什么的对象,触发action是唯一改变state的方式,action本质上是一个普通js对象,必必须一个type字段,值为字符串常量
  • action creator】创建action
  • reducer】描述action如何改变state

Redux常用方法

createStore(reducer, [initialState])

创建一个Redux store来以存放应用中所有的state

combineReducers(reducers)

随着应用复杂度上升,数据源逐渐变混乱,导致组件内部数据调用十分复杂,会产生数据冗余或者混用等情况,需要对reducer函数进行拆分,拆分后的每一模块独立负责管理state的一部分。combineReducers函数的作用是,返回一个最终的rootReducer函数,rootReducer做的事情是得到一个由多个不同reducer函数作为value(key可以自定义)的对象。

    const module1Reducer = combineReducers(
        module1_key1: module1_reducer1
    );
    const rootReducer = combineReducers(
        key1: reducer1,
        module1: module1Reducer
    );
    
    const store = createStore(rootReducer);
    
    // store中保存的state结构如下
    {
    	key1: reducer1(state.key1, action),
        module1: {
            module1_key1: (state.module1.module1_key1, action)
        }
    }
   

总结一下,state对象的结构由传入的多个reducer的key决定,可以根据模块拆分的细粒度,考虑是否需要嵌套使用combineReducers,整个应用的数据大致分来两类:普通data和ui状态

    + data
    	- 服务器响应的数据
    	- 缓存数据
    	- 本地尚未持久化到服务器的数据
    	- 用户输入
    	- ...
    + ui状态
    	- 激活的路由
    	- 被选中的Tab标签
    	- 是否显示加载动画
    	- 分页器状态
    	- ...

设计state结构时,尽量把state范式化,不要存在嵌套不同类型的对象的情况。把数据放到一个对象(列表)中,每个数据用id作为主键。不同类型的对象通过id引用数据,这样数据发生改变的时候,只需要修改一处地方,减少数据冗余或者混用。

applyMiddleware(...middlewares)

首先要介绍一下什么是middlewaresmiddlewares用于包装store.dispatch,扩展其功能,在发起action之后,到达reducer之前执行一些逻辑,有点类似是aop的一种实现。
applyMiddleware大致实现:

  • 暂存redux store提供的dispatch
  • dispatch作为实参,传给middleware执行之后返回的函数A
  • 执行函数A,返回包装过的dispatch,覆盖原来的store.dispatch
    function applyMiddleware(store, middlewares) {
        middlewares = middlewares.slice()
        middlewares.reverse()
        // 暂存dispatch
        let dispatch = store.dispatch
        // 包装dispatch
        middlewares.forEach(middleware =>
            dispatch = middleware(store)(dispatch)
        )
        return {...store, { dispatch })
    }

理解了applyMiddleware的逻辑,自定义一个middleware大致如下

    function(store){
        // pass store.dispatch to next 
        return function(next){
            // return dispatch
            return function(action){
                // implement middleware logic
            }
        }
    }

bindActionCreators(actionCreators, dispatch)

  • 参数actionCreators如果为函数 把 action creators 转成拥有同名keys的对象,但使用 dispatch 把每个action creator包围起来,返回新的对象
  • 参数actionCreators如果为对象,若actionCreators[key]为函数,用dispatch把每个 actionCreators[key]包围起来,返回新的对象

compose(...functions)

组合store enhance,applyMiddleware 和 redux-devtools就是store enhance


React-redux桥接

Redux本身只提供应用状态和数据流管理,除了和React一起用外,还支持其它界面库,且没有任何依赖。要在React的项目中使用Redux,比较好的方式是借助react-redux这个库来做连接.

provider

为整个应用提供store数据,做的事情是把store作为props传递到每一个被connet()包装的组件

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

connect(...args)(component)返回一个与Redux store连接的组件类,下面简单讲解一下connect方法的参数

mapStateToProps(state, [ownProps]) [Function]
  • statestore中整个应用的state
  • ownProps高阶函数包装过的组件的props
  • 该回调函数必须返回一个纯对象,这个对象会与被包装的组件的props做merge合并
  • mapStateToProps可以做一些数据的format, filter,compose操作,保证数据在组件层面的方便使用
mapDispatchToProps(dispatch, [ownProps]) [Object or Function]
  • mapDispatchToProps参数为Object, 每个定义在该对象的函数都将被当作Redux action creator,其中所定义的方法名将作为属性名,合并到被包装的组件的props中,实现的效果:执行component.prop.checkout()实际上是dispatch了一个action,这样做的好处是component与redux的解耦,component根本不知道redux的存在。
	// action creator返回对象 
	mapDispatchToProps = {
		// action creator
		checkout: function actionCreator(productId){
			return {
    				type: types.ADD_TO_CART,
    				productId
  			}	
		}
	}
	// action creator返回函数,thunk
	mapDispatchToProps = {
		// action creator
		checkout: function actionCreator(productId){
			action creator 返回一个thunk, thunk的参数为 dispatch 和 getState
			return (dispatch, getState) => {
  				
                if (getState().products.byId[productId].inventory > 0) {
  
   					  dispatch(addToCartUnsafe(productId))
  				
                }
			
            }
		}
	}

	// 最终被绑定的组件props
	component.props.checkout = function () {
    	return dispatch(actionCreator.apply(undefined, arguments));
  	}
  • mapDispatchToProps参数为Function
	mapDispatchToProps(dispatch, [ownProps]) = function(){
		return {
			checkout: () => {
  				
                dispatch(actionCreator())
		
            }
		}
	}		
	// 最终被绑定的组件.props
	component.props.checkout = function () {
    		return dispatch(actionCreator.apply(undefined, arguments));
  	}

	// 使用bindActionCreators
	mapDispatchToProps(dispatch, [ownProps]) = function(){
		return bindActionCreators(actionCreator, dispatch)
	}
	// 最终被绑定的组件props
	component.props.actionCreator = function () {
    	return dispatch(actionCreator.apply(undefined, arguments));
  	}

	// 使用 bindActionCreators
	mapDispatchToProps(dispatch, [ownProps]) = function(){
		return bindActionCreators({
			‘checkout’: actionCreator
		}, dispatch)
	}
	// 最终被绑定的组件props
	component.props.checkout = function () {
    	        return dispatch(actionCreator.apply(undefined, arguments));
  	}

使用redux之后应用的数据流向

dispatch(actionCreator) => Reducer => (state, action) => state

redux_data_flow_jpeg

  • 用户操作或者网络请求 store.dispatch(action)
  • redux store调用传入的rootReducer
  • redux 执行全部的reducer,把每个reducer执行得到的结果输出合成一个新的对象
  • store 存储rootReducer返回的值,更新currentState

pwa学习笔记-pwa初体验

PWA是什么?

Progressive Web Apps (PWA)是Google在移动时代力推的一个提升webapp性能和体验解决方案,官方列举了几大新特点:

  • 渐进式 - 适用于所有浏览器,因为它是以渐进式增强作为宗旨开发的
    — 连接无关性 - 能够借助 Service Worker 在离线或者网络较差的情况下正常访问
  • 类似应用 - 由于是在 App Shell 模型基础上开发,因为应具有 Native App 的交互和导航,给用户 Native App 的体验
  • 持续更新 - 始终是最新的,无版本和更新问题
  • 安全 - 通过 HTTPS 协议提供服务,防止窥探和确保内容不被篡改
  • 可索引 - 应用清单文件和 Service Worker 可以让搜索引擎索引到,从而将其识别为『应用』
  • 粘性 - 通过推送离线通知等,可以让用户回流
  • 可安装 - 用户可以添加常用的 webapp 到桌面,免去去应用商店下载的麻烦
  • 可链接 - 通过链接即可分享内容,无需下载安装

个人理解核心目的增强WebApp的体验:

  • 站点可添加至主屏幕(强提示),缩短操作路径
  • 使用 App Shell模型,应用达到秒开效果
  • 全屏方式运行
  • 支持离线缓存
  • 消息推送

核心技术

Service Worker

Service Worker�是实现PWA应用最重要的一项技术,是一个在用户没有打开网页没有交互行为时运行在浏览器后台进程中的脚本,提供管理资源�缓存、拦截资源、消息推送通知�请求等功能。
阅读下面内容前你需要�做一些预备知识

Service_Worker_API
Cache_API

首先我们需要检查浏览器是否支持service worker

if ('serviceWorker' in navigator) {
    navigator.serviceWorker
             .register('./service-worker.js')
             .then(function() { console.log('Service Worker Registered'); });
  }

注意service-worker.js文件应用的根文件夹内,因为服务工作线程的作用域由该文件所在的目录定义。
在service worker中,我们通过cache相关的api�缓存静态资源或者请求数据,从而达到应用秒开的效果。

首先,我们在service worker被install之后,通过 caches.open() 打开一个指定key的缓存。缓存key可让我们对资源进行版本控制。实际使用时,我们一般会将数据与 App Shell(静态资源) 分开,以便我们能轻松地更新某个数据,而不会影响其他数据。

缓存打开后,我们便可调用 cache.addAll(),这个带有网址列表参数的方法随即从服务器获取文件,并将响应添加到缓存内。遗憾的是,cache.addAll() 具有原子性,如果任何一个文件失败,整个缓存步骤也将失败!

var cacheName = 'cache-1';
var filesToCache = [
    '/scripts/app.js',
    '/styles/inline.css',
    '/images/clear.png',
];

self.addEventListener('install', function(e) {
  console.log('[ServiceWorker] Install');
  e.waitUntil(
    caches.open(cacheName).then(function(cache) {
      console.log('[ServiceWorker] Caching app shell');
      return cache.addAll(filesToCache);
    })
  );
});

重新加载页面之后,打开 DevTools,转至 Application 面板的 Cache Storage 窗格,可以查看所有被缓存的资源
image.png

上面提到缓存分为静态资源和请求数据。在访问页面时,我们拦截发送的请求,并将其响应存储在缓存内。对于静态资源我们再次访问页面时,如果资源版本没有修改,就可以直接命中缓存方便。而对于请求的数据我们采用缓存优先于网络策略,我们期望网络响应成为“可信来源”,始终能够为我们提供最新信息。在第一次访问页面时,缓存数据解决了webapp内容区域空窗的问题,让用户体验更好。

self.addEventListener('fetch', function(e) {
  // console.log('[Service Worker] Fetch', e.request.url);
  var dataUrl = 'https://query.yahooapis.com/v1/public/yql';
  if (e.request.url.indexOf(dataUrl) > -1) {
    /*
     * When the request URL contains dataUrl, the app is asking for fresh
     * weather data. In this case, the service worker always goes to the
     * network and then caches the response. This is called the "Cache then
     * network" strategy:
     * https://jakearchibald.com/2014/offline-cookbook/#cache-then-network
     */
    e.respondWith(
      caches.open(dataCacheName).then(function(cache) {
        console.log('dataCacheName', cache)
        return fetch(e.request).then(function(response){
          cache.put(e.request.url, response.clone());
          return response;
        });
      })
    );
  } else {
    /*
     * The app is asking for app shell files. In this scenario the app uses the
     * "Cache, falling back to the network" offline strategy:
     * https://jakearchibald.com/2014/offline-cookbook/#cache-falling-back-to-network
     */
    e.respondWith(
      caches.match(e.request).then(function(response) {
        return response || fetch(e.request);
      })
    );
  }
});

对于缓存,它是一把双刃剑,它带来了出色体验,也会面临一些新的难题-缓存更新。在生产环境中,google官方推荐sw-precache生成service worker代码,它内置了缓存更新策略,可以确保将资源过期的请求发送到服务器。

App Shell

App Shell 是启动PWA应用用户界面所需的最小的资源(HTML、CSS 和 JavaScript)。Shell文件一旦通过网络完成加载,就会通过�service workder保存到缓存中。以后每当用户打开应用时,就会自动从本地的缓存中打开Shell文件,确保加载速度非常快。
image.png

Add To Home Screen

PWA应用允许用户将轻松得将使用的webapp添加到系统的主屏幕,而且这个功能实现十分简单,浏览器内核帮我们处理了大量复杂的内部工作,我们只需要在应用根目录下添加一个应用清单文件manifest.json
供参考的模板

{
  "name": "Weather",
  "short_name": "Weather",
  "icons": [{
    "src": "images/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    }, {
      "src": "images/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    }, {
      "src": "images/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    }, {
      "src": "images/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    }, {
      "src": "images/icons/icon-256x256.png",
      "sizes": "256x256",
      "type": "image/png"
    }],
  "start_url": "/index.html",
  "display": "standalone",
  "background_color": "#3E4EB8",
  "theme_color": "#2F3BA2"
}

manifest.json可以描述webapp不懂的展现形式

  • 以全屏模式启动
  • 控制屏幕方向以获得最佳查看效果
  • 定义网站的“启动画面”启动体验和主题颜色
  • 追踪您是从主屏幕还是从网址栏启动

兼容性

当我们提PWA应用兼容性问题时,我们说的其实是PWA背后的新特性在各大浏览器厂商的支持程度。

在当前时间点来看这个问题,总体还是抱乐观态度,国内外的大部分浏览器都已经实现PWA必需的关键技术,所有剩余技术目前都处于开发阶段。

对于最大的绊脚石safari,苹果也2018年1月宣布将在 iOS 11.3 和 macOS 10.13.4 版本上正式增加对 Service Worker 的支持,同时还支持了添加到桌面(Web App Manfiest)。

兼容性参考,数据每两周更新一次

最后

行业内flipboardflipkart阿里巴巴国际站饿了么都已经落地PWA,百度开源了基于Vue的PWA解决方案lavas。PWA应用正在将迎来春天,成为趋势。
对PWA感兴趣的同学可以一起探讨学习

前端权限控制方案

在做商家后台管理系统时,作为前端通常会设计到大量的权限控制问题,按照细粒度归归类大致可以分类以下三类

  1. 页面权限
  2. 模块权限-页面区块(组件)是否显示
  3. 元件权限-组件内元素是否显示

以往的处理方式

后端会将用户权限数据同步注入到VM模板中或者前端发送异步请求取到权限数据,数据消费场景一般都散落在代码的角角落落。

    // 伪代码
    render(){
        return {window.permission?<Component/>:null}
    }

    render(){
        return <Component>{this.props.permission?<Button>删除</Button>: null}</Component>
    }

用这种方式实现的代码,执行上没有问题,也达到了业务的需求。但是随着代码量的递增,代码变得难以维护,特别是新接手的同学,简直是一场噩梦。

React体系下的实现方式

页面权限、模块权限、元件权限三种前端权限表现形式对应不同的管理策略。

页面权限

对于传统的多页应用,页面权限控制不需要前端关心,后端路由做一层控制。在SPA架构的前端应用中,我们的思路是将所有的前端路由配置在后端,对于不同角色的用户,后端把路由列表吐给前端注册。

模块权限、元件权限

对于这两类权限控制的事就全部需要交给前端处理了,大致思路是将系统中用户散落的权限统一配置,通过HOC包装一下React组件,提供劫持渲染和权限透传的能力。

统一管理权限registerAuthRules

应用的所有权限配置会被统一配置在一个闭包中,权限的值支持后端同步吐出,也支持每次异步获取(利用Promise实现)

// 伪代码
export const AUTH_RULES = {
    'isX1': window.isX1 === '',
    'isX2': window.isX2 === '',
    'isX3': () => {
        return new Promise((resolve, reject) => {
            resolve(result); // resolve的参数只能是true或者false
        })
    },
};

registerAuthRules(AUTH_RULES);

权限规则表达式

权限列表中配置的只是颗粒度最细的单个权限。在实际业务需求中,我们常需要根据权限格则组合结果,决定是否显示。比如ComponentA的显示条件是isX1 && isX2 或者 isX1 || isX3。
这里需要引入权限规则表达式的概念。How to compute?
第一步:利用词法分析器解析出表达式中有多少个权限变量。利用esprima可以轻松取到
第二步:计算每个变量对应的权限值
第三部:计算规则表达式,因为权限规则有可能是异步或者的,这里将每个格则包装成Promise对象,利用Promise.all做统一返回,在成功的回调函数中通过New Function的方式计算字符串表达式的结果

// 计算表达式相关代码
function getExpressionValue(expression, data) {
    const codes = [];
    for (const key in data) {
        if (data.hasOwnProperty(key)) {
            const value =
                typeof data[key] === 'string' ? `"${data[key]}"` : data[key];
            codes.push(`var ${key} = ${value};`);
        }
    }
    codes.push(`return ${expression};`);
    return new Function(codes.join(''))();
}

如何使用

registerAuthRules

注册权限规则列表,支持同步规则和异步规则
参数:

  • rules {Object} 应用权限规则MAP

registerComponentRules

注册组件显示规则,根据组件displayName配置组件所需权限列表
参数:

  • rules {Object} 组件权限规则MAP

调用查看

    export const COMPONENTS_RULES = {
        ComponentA: 'isX1',
        ComponentB: 'isX1 && isX2',
    };
    registerComponentRules(COMPONENTS_RULES)

Auth HOC函数

参数:

  • options {Object} 组件权限规则MAP
  • options.placeholder {Component} 组件隐藏时的占位节点;默认为noscript
  • options.initialHide {Boolean} 当存在异步权限规则时,组件是否先默认隐藏;默认值为true
  • options.rules {Object} 配置组件需要权限规则集合,作为props属性$auth传递给组件
1. 组件级别权限控制

根据WrappedComponent.displayName判断组件是否有权限

class Component{
  // ...
}
Component.displayName = 'ComponentA';

const Authed_Component_1 = Auth({
  placeholder: <p>无权限的占位节点</p>
})(Component)
2. 组件内部权限控制(权限属性模式)
class Page{
  render(){
    const {$auth} = this.props;
    return (
      <div>
        { $auth.isShowDeleteBtn && <p>删除</p> }
      </div>
    )
  }
}
// 权限校验条件与权限属性,组件内容没有校验逻辑
const Authed_Page = Auth({
  rules: {
    'isShowDeleteBtn': 'isVip'
  }
})(Page);

代码实现hoc-auth

博客搬家

博客搬家

Github的issue来做博客方便一点:

  1. 支持图片拖拽上传。
  2. 全屏的Markdown语法编辑器。
  3. 用issue的label来做issue分类管理。
  4. 连评论也是Markdown语法的。
  5. 不要自己做SEO。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.