{"action":"created","issue":{"url":"https://api.github.com/repos/xiaoxiaoaobama/_blog/issues/2","repository_url":"https://api.github.com/repos/xiaoxiaoaobama/_blog","labels_url":"https://api.github.com/repos/xiaoxiaoaobama/_blog/issues/2/labels{/name}","comments_url":"https://api.github.com/repos/xiaoxiaoaobama/_blog/issues/2/comments","events_url":"https://api.github.com/repos/xiaoxiaoaobama/_blog/issues/2/events","html_url":"https://github.com/xiaoxiaoaobama/_blog/issues/2","id":306234621,"number":2,"title":"ant-design-pro Authorized权限组件设计","user":{"login":"xiaoxiaoaobama","id":26184778,"avatar_url":"https://avatars3.githubusercontent.com/u/26184778?v=4","gravatar_id":"","url":"https://api.github.com/users/xiaoxiaoaobama","html_url":"https://github.com/xiaoxiaoaobama","followers_url":"https://api.github.com/users/xiaoxiaoaobama/followers","following_url":"https://api.github.com/users/xiaoxiaoaobama/following{/other_user}","gists_url":"https://api.github.com/users/xiaoxiaoaobama/gists{/gist_id}","starred_url":"https://api.github.com/users/xiaoxiaoaobama/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xiaoxiaoaobama/subscriptions","organizations_url":"https://api.github.com/users/xiaoxiaoaobama/orgs","repos_url":"https://api.github.com/users/xiaoxiaoaobama/repos","events_url":"https://api.github.com/users/xiaoxiaoaobama/events{/privacy}","received_events_url":"https://api.github.com/users/xiaoxiaoaobama/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":2,"created_at":"2018-03-18T12:07:28Z","updated_at":"2018-04-07T13:50:41Z","closed_at":null,"author_association":"OWNER","body":"本文将介绍pro中Authorized权限组件的实现思路、在系统中的应用以及个人对组件设计的思考和感想，如有不正确的地方，欢迎指正。\r\n\r\n首先来看下权限组件的实现思路：现有权限(currentAuthorize)和准入权限(authorize)\r\n\r\n做比较，如果一致，则渲染和准入权限匹配的组件。\r\n\r\n思路很简单，我们先不关心Authorized权限组件的具体细节，用一段代码来实现上面描述的功能：\r\n```javascript\r\n<div>\r\n    { \r\n        currentAuthorize === authorize\r\n        ? <MatchComponent>\r\n        : <NoMatchComponent>\r\n    }\r\n</div>\r\n```\r\n先来分析一下上述代码存在的问题：\r\n\r\n系统中的多个模块都需要进行相应的权限设置，也就是说上面的代码需要在多处调用，一方面是代码重复冗余，更可怕的是一旦判断逻辑currentAuthorize === authorize变化，所有模块的判断逻辑都需要作出相应的更改。\r\n\r\n这样的实现方式很不react的，react推崇的是声明式编程（不理解的小伙伴可以看下程墨老师关于声明式编程的一篇文章从年会看声明式编程(Declarative Programming)，对我启发很大）：react对用户编写的组件提供了统一管理的能力，说的具体点就是用户不需要在每个组件中书写生命周期实现，真实dom节点对应等代码，这些和组件相关的功能代码已经被react统一管理了，用户使用组件时只需要关心渲染什么就可以了，根本不需要操心如何渲染。这就是声明式编程的思想。\r\n\r\n声明式编程减少了重复劳动：组件相关的功能代码在一个地方书写就可以了，不需要在每个组件中都来实现。\r\n声明式编程提高了代码的可维护性：react未来可能会优化组件相关的功能代码，但是react的组件代码是不需要改变的。\r\n基于此原因，我们用声明式编程的思想对权限代码进行重写，将权限的逻辑判断代码封装在react组件中统一维护管理(像是react-route库中将路由处理的代码封装在了route组件中统一维护，这些都是符合声明式编程思想的)：\r\n```javascript\r\n//权限组件使用：\r\n<Authorized \r\n    currentAuthorize={'admin'}\r\n    authorize={['admin','guest']}\r\n    noMatch={<div>无权访问</div>}\r\n/>\r\n    <div>权限通过后显示的内容</div>\r\n</Authorized> \r\n```\r\n有一点具体说明：用户在当前系统中会被分配相应的角色信息，在登录成功后返回。使用Authorized组件时currentAuthorize属性是不需要重复传的，引用时传递一次即可(消灭重复代码)，实现方案如下：\r\n```javascript\r\nimport Authorized from './Authorized';\r\n\r\n//每次引入组件之前调用函数传入currentAuthorize，\r\n//通过组件内部变量来维护currentAuthorize。\r\nlet CURRENT = 'NULL';\r\n\r\nconst renderAuthorize = (currentAuthority) => {\r\n  CURRENT = currentAuthority;\r\n  return Authorized;\r\n};\r\n\r\nexport { CURRENT };\r\nexport default renderAuthorize;\r\nAuthorized组件内部实现：\r\n\r\n//Authorized.js\r\nimport React from 'react';\r\nimport CheckPermissions from './CheckPermissions';\r\n\r\nclass Authorized extends React.Component {\r\n  render() {\r\n    const { children, authority, noMatch = null } = this.props;\r\n    const childrenRender = typeof children === 'undefined' ? null : children;\r\n    //现有权限和准入权限比对\r\n    //CheckPermissions返回childrenRender或者noMatch\r\n    return CheckPermissions(authority, childrenRender, noMatch);\r\n  }\r\n}\r\n\r\nexport default Authorized;\r\n\r\n//CheckPermissions.js\r\nimport React from 'react';\r\nimport { CURRENT } from './index';\r\n\r\nconst checkPermissions = (authority, currentAuthority, target, Exception) => {\r\n  // 没有判定权限.默认查看所有\r\n  // Retirement authority, return target;\r\n  if (!authority) {\r\n    return target;\r\n  }\r\n  // 数组处理\r\n  if (Array.isArray(authority)) {\r\n    if (authority.indexOf(currentAuthority) >= 0) {\r\n      return target;\r\n    }\r\n    return Exception;\r\n  }\r\n\r\n  // string 处理\r\n  if (typeof authority === 'string') {\r\n    if (authority === currentAuthority) {\r\n      return target;\r\n    }\r\n    return Exception;\r\n  }\r\n\r\n  // Function 处理\r\n  if (typeof authority === 'function') {\r\n    try {\r\n      const bool = authority(currentAuthority);\r\n      if (bool) {\r\n        return target;\r\n      }\r\n      return Exception;\r\n    } catch (error) {\r\n      throw error;\r\n    }\r\n  }\r\n  throw new Error('unsupported parameters');\r\n};\r\n\r\nexport { checkPermissions };\r\n\r\nconst check = (authority, target, Exception) => {\r\n  return checkPermissions(authority, CURRENT, target, Exception);\r\n};\r\n\r\nexport default check;\r\n```\r\n这样一个基本的权限组件就封装完成了，我们结合业务看一下在系统中的应用：\r\n```javascript\r\n//route.js\r\n//登录模块路由\r\n<Route\r\n    path=\"/user\"\r\n    component={UserLayout}\r\n/>\r\n\r\n//主模块路由，用户未登录(currentAuthorized为guest)重定向到登录页。\r\n<Authorized \r\n    authorize={['admin']}\r\n    noMatch={<Route path=\"/\" render={() => <Redirect to={'/user/login'} />} />}\r\n>\r\n    <Route path=\"/\" component={BasicLayout} />\r\n</Authorized>\r\n```\r\n主模块中每个功能模块都要做一次权限路由判断，所以我们根据当前的具体业务扩展一下权限组件：\r\n```javascript\r\n//AuthorizedRoute.js\r\nimport React from 'react';\r\nimport { Route, Redirect } from 'react-router-dom';\r\nimport Authorized from './Authorized';\r\n\r\n//路由权限组件封装了noMatch和children的传入值，省去了route和redirect组件的配置。\r\n//用户在使用时只需传入path及对应的重定向地址。\r\nexport default ({authority, component: Component, redirectPath, render, ...rest}) => {\r\n    return (\r\n        <Authorized \r\n            authorize={authority}\r\n            noMatch={\r\n                <Route \r\n                    {...rest}\r\n                    render={() => <Redirect to={redirectPath} />}\r\n                />\r\n            }\r\n        >\r\n            <Route\r\n              {...rest}\r\n              render={(props) => \r\n                (Component ? <Component {...props} /> : render(props))  \r\n              }\r\n            />\r\n        </Authorized>\r\n    )\r\n}\r\n\r\n//route.js\r\n<Route\r\n    path=\"/user\"\r\n    component={UserLayout}\r\n/>\r\n{/* <Authorized \r\n    authorize={['admin']}\r\n    noMatch={<Route path=\"/\" render={() => <Redirect to={'/user/login'} />} />}\r\n>\r\n    <Route path=\"/\" component={BasicLayout} />\r\n</Authorized> */}\r\n//\r\n<AuthorizedRoute\r\n   path=\"/\"\r\n   render={props => <BasicLayout {...props} />}\r\n   authority={['admin', 'user']}\r\n   redirectPath=\"/user/login\"\r\n/>\r\n```\r\n最后将扩展组件挂载到组件函数上，完整的index.js文件如下:\r\n```javascript\r\nimport Authorized from './Authorized';\r\nimport AuthorizedRoute from './AuthorizedRoute';\r\n\r\nlet CURRENT = 'NULL';\r\n\r\nAuthorized.AuthorizedRoute = AuthorizedRoute;\r\n\r\nconst renderAuthorize = (currentAuthority) => {\r\n  if (currentAuthority) {\r\n    if (currentAuthority.constructor.name === 'Function') {\r\n      CURRENT = currentAuthority();\r\n    }\r\n    if (currentAuthority.constructor.name === 'String') {\r\n      CURRENT = currentAuthority;\r\n    }\r\n  } else {\r\n    CURRENT = 'NULL';\r\n  }\r\n  return Authorized;\r\n};\r\n\r\nexport { CURRENT };\r\nexport default renderAuthorize;\r\n```\r\n以上是本人对pro中Authorized组件设计的一点感想，大家在阅读源码时一定不要只停留在表层实现，要深入的思考这样的设计是为了解决开发中的哪些痛点。\r\n\r\n感谢花时间阅读文章，后续会整理更多关于pro的文章，敬请关注。"},"comment":{"url":"https://api.github.com/repos/xiaoxiaoaobama/_blog/issues/comments/379470755","html_url":"https://github.com/xiaoxiaoaobama/_blog/issues/2#issuecomment-379470755","issue_url":"https://api.github.com/repos/xiaoxiaoaobama/_blog/issues/2","id":379470755,"user":{"login":"xiaoxiaoaobama","id":26184778,"avatar_url":"https://avatars3.githubusercontent.com/u/26184778?v=4","gravatar_id":"","url":"https://api.github.com/users/xiaoxiaoaobama","html_url":"https://github.com/xiaoxiaoaobama","followers_url":"https://api.github.com/users/xiaoxiaoaobama/followers","following_url":"https://api.github.com/users/xiaoxiaoaobama/following{/other_user}","gists_url":"https://api.github.com/users/xiaoxiaoaobama/gists{/gist_id}","starred_url":"https://api.github.com/users/xiaoxiaoaobama/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xiaoxiaoaobama/subscriptions","organizations_url":"https://api.github.com/users/xiaoxiaoaobama/orgs","repos_url":"https://api.github.com/users/xiaoxiaoaobama/repos","events_url":"https://api.github.com/users/xiaoxiaoaobama/events{/privacy}","received_events_url":"https://api.github.com/users/xiaoxiaoaobama/received_events","type":"User","site_admin":false},"created_at":"2018-04-07T13:50:41Z","updated_at":"2018-04-07T13:50:41Z","author_association":"OWNER","body":"//models文件夹下的login.js文件：\r\n*login({ payload }, { call, put }) {\r\n      const response = yield call(fakeAccountLogin, payload);\r\n      yield put({\r\n        type: 'changeLoginStatus',\r\n        payload: response,\r\n      });\r\n      if (response.status === 'ok') {\r\n        //登录成功后call一下权限接口\r\n        const response = yield call(test, payload);\r\n        console.log('异步拿到返回值')\r\n        console.log(response);\r\n        reloadAuthorized();\r\n        yield put(routerRedux.push('/'));\r\n      }\r\n}"}}
{"actor":{"display_login":"xiaoxiaoaobama"}}