首页
归档
关于
友人帐
Search
1
Mac OS 终端利器 iTerm2 + Oh My Zsh
14,185 阅读
2
前端 History 路由及 API 反向代理的 Nginx 配置
10,669 阅读
3
解决 Xcode Command Line Tools 错误或无法安装问题
6,588 阅读
4
Mac下右键使用VSCode打开项目
4,010 阅读
5
BrowserHistory刷新页面404问题
3,509 阅读
码上世界
内容分享
生活印记
其他
登录
Search
小小孩
累计撰写
95
篇文章
累计收到
185
条评论
首页
栏目
码上世界
内容分享
生活印记
其他
页面
归档
关于
友人帐
搜索到
48
篇与
码上世界
的结果
2019-01-18
Vue中的eventBus使用
使用 eventBus在项目入口文件main.js中 加入如下代码// EventBus window.eventBus = new Vue();这是引入全局变量eventBus准备两个需要通信的 vue 组件About.vue<template> <div class="about"> <h1>This is an {{msg}} page</h1> <h2>other :{{other}}</h2> </div> </template> <script> export default { data() { return { msg: "about", other: "old other" }; }, created() { eventBus.$on("postData", data => { this.other = data; }); } }; </script>代码中 data 里的 other 值一开始是 old other,这个组件需要另一个组件返回过来的值来进行更新。如下代码则是取回 键值是postData 的数据eventBus.$on("postData", data => { this.other = data; });Test.vue<template> <div class="test"> <h1>This is an {{msg}} page</h1> </div> </template> <script> export default { data() { return { msg: "test" }; }, methods: {}, destroyed() { eventBus.$emit("postData", "new other"); } }; </script>以下代码是在组件销毁之前给键值postData 注入值 new othereventBus.$emit("postData", "new other");效果所以效果就是一开始进入 about 页面时 other 值为 old other 进入 test 页面 再返回时 other 值则变成了 new other
2019年01月18日
348 阅读
0 评论
0 点赞
2019-01-16
CSS小技巧小汇
一、flex 项中子元素文本截断 text-overflow:ellipsis 失效,给你一个完美的解决方案。flexbox 布局时 flex 项子元素中的文本溢出后想显示省略号(…),结果设置 text-overflow:ellipsis 后没效果,我们来看代码:HTML 代码:<div class="flex"> <div class="col1">图标</div> <div class="col2"> <div class="item-con"> flexbox 布局旨在提供一个更有效地布局、对齐方式,并且能够使容器中的子元素大小未知或动态变化情况下仍然能够分配好子元素之间的空间。 </div> </div> </div> CSS 代码:.flex { display: flex; } .flex .col1 { margin-right: 6px; } .flex .col2 { flex: 1; } .flex .col2 .item-con { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }完整的 DEMO :我们看到 .col2 设置 flex: 1; , .item-con 设置了 text-overflow:ellipsis ,但是文本还是溢出了。这种布局在移动端页面开发时候经常遇到,我们不能为 .item-con 元素设置个宽度,这样就无法适应不同屏幕尺寸的终端设备。解决方案:在 flex项中设置 min-width: 0;解决方案是在 flex 项(上例子中 .col2 元素)中设置 min-width: 0;,当然你也可以设置其他合适的 min-width值。二、css 文本多行显示,超出省略号表示.text { width: 200px; word-break: break-all; text-overflow: ellipsis; display: -webkit-box; /** 对象作为伸缩盒子模型显示 **/ -webkit-box-orient: vertical; /** 设置或检索伸缩盒对象的子元素的排列方式 **/ -webkit-line-clamp: 3; /** 显示的行数 **/ overflow: hidden; /** 隐藏超出的内容 **/ }三、进度条动画<style> .process { width: 200px; height: 20px; border-radius: 10px; background: repeating-linear-gradient( -45deg, #fafafa 25%, #f93e3e 0, #f93e3e 50%, #fafafa 0, #fafafa 75%, #f93e3e 0 ); background-size: 15px 15px; animation: panoramic 8s linear infinite; } @keyframes panoramic { to { background-position: 200%; } } </style> <div class="process"> <div></div> </div>
2019年01月16日
2,400 阅读
0 评论
0 点赞
2019-01-12
一篇看懂 React Hooks
什么是 React HooksReact Hooks 是 React 16.7.0-alpha 版本推出的新特性,想尝试的同学安装此版本即可。__React Hooks 要解决的问题是状态共享__,是继 render-props 和 higher-order components 之后的第三种状态共享方案,不会产生 JSX 嵌套地狱问题。这个状态指的是状态逻辑,所以称为__状态逻辑复用__会更恰当,因为只共享数据处理逻辑,不会共享数据本身。不久前精读分享过的一篇 Epitath 源码 - renderProps 新用法 就是解决 JSX 嵌套问题,有了 React Hooks 之后,这个问题就被官方正式解决了。为了更快理解 React Hooks 是什么,先看笔者引用的下面一段 renderProps 代码:function App() { return ( <Toggle initial={false}> {({ on, toggle }) => ( <Button type="primary" onClick={toggle}> Open Modal </Button> <Modal visible={on} onOk={toggle} onCancel={toggle} /> )} </Toggle> ) }恰巧,React Hooks 解决的也是这个问题:function App() { const [open, setOpen] = useState(false); return ( <> <Button type="primary" onClick={() => setOpen(true)}> Open Modal </Button> <Modal visible={open} onOk={() => setOpen(false)} onCancel={() => setOpen(false)} /> </> ); }可以看到,React Hooks 就像一个内置的打平 renderProps 库,我们可以随时创建一个值,与修改这个值的方法。看上去像 function 形式的 setState,其实这等价于依赖注入,与使用 setState 相比,__这个组件是没有状态的__。React Hooks 的特点React Hooks 带来的好处不仅是 “更 FP,更新粒度更细,代码更清晰”,还有如下三个特性:多个状态不会产生嵌套,写法还是平铺的(renderProps 可以通过 compose 解决,可不但使用略为繁琐,而且因为强制封装一个新对象而增加了实体数量)。Hooks 可以引用其他 Hooks。更容易将组件的 UI 与状态分离。第二点展开说一下:Hooks 可以引用其他 Hooks,我们可以这么做:import { useState, useEffect } from "react"; // 底层 Hooks, 返回布尔值:是否在线 function useFriendStatusBoolean(friendID) { const [isOnline, setIsOnline] = useState(null); function handleStatusChange(status) { setIsOnline(status.isOnline); } useEffect(() => { ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; } // 上层 Hooks,根据在线状态返回字符串:Loading... or Online or Offline function useFriendStatusString(props) { const isOnline = useFriendStatusBoolean(props.friend.id); if (isOnline === null) { return "Loading..."; } return isOnline ? "Online" : "Offline"; } // 使用了底层 Hooks 的 UI function FriendListItem(props) { const isOnline = useFriendStatusBoolean(props.friend.id); return ( <li style={{ color: isOnline ? "green" : "black" }}>{props.friend.name}</li> ); } // 使用了上层 Hooks 的 UI function FriendListStatus(props) { const statu = useFriendStatusString(props.friend.id); return <li>{statu}</li>; }这个例子中,有两个 Hooks:useFriendStatusBoolean 与 useFriendStatusString, useFriendStatusString是利用 useFriendStatusBoolean 生成的新 Hook,这两个 Hook 可以给不同的 UI:FriendListItem、FriendListStatus 使用,而因为两个 Hooks 数据是联动的,因此两个 UI 的状态也是联动的。顺带一提,这个例子也可以用来理解 对 React Hooks 的一些思考 一文的那句话:__“有状态的组件没有渲染,有渲染的组件没有状态”__:useFriendStatusBoolean 与 useFriendStatusString 是有状态的组件(使用 useState),没有渲染(返回非 UI 的值),这样就可以作为 __Custom Hooks__ 被任何 UI 组件调用。FriendListItem 与 FriendListStatus 是有渲染的组件(返回了 JSX),没有状态(没有使用 useState),这就是一个纯函数 UI 组件,利用 useState 创建 ReduxRedux 的精髓就是 Reducer,而利用 React Hooks 可以轻松创建一个 Redux 机制:// 这就是 Redux function useReducer(reducer, initialState) { const [state, setState] = useState(initialState); function dispatch(action) { const nextState = reducer(state, action); setState(nextState); } return [state, dispatch]; }这个自定义 Hook 的 value 部分当作 redux 的 state,setValue 部分当作 redux 的 dispatch,合起来就是一个 redux。而 react-redux 的 connect 部分做的事情与 Hook 调用一样:// 一个 Action function useTodos() { const [todos, dispatch] = useReducer(todosReducer, []); function handleAddClick(text) { dispatch({ type: "add", text }); } return [todos, { handleAddClick }]; } // 绑定 Todos 的 UI function TodosUI() { const [todos, actions] = useTodos(); return ( <> {todos.map((todo, index) => ( <div>{todo.text}</div> ))} <button onClick={actions.handleAddClick}>Add Todo</button> </> ); }useReducer 已经作为一个内置 Hooks 了,在这里可以查阅所有 内置 Hooks。不过这里需要注意的是,每次 useReducer 或者自己的 Custom Hooks 都不会持久化数据,所以比如我们创建两个 App,App1 与 App2:function App1() { const [todos, actions] = useTodos(); return <span>todo count: {todos.length}</span>; } function App2() { const [todos, actions] = useTodos(); return <span>todo count: {todos.length}</span>; } function All() { return ( <> <App1 /> <App2 /> </> ); }这两个实例同时渲染时,并不是共享一个 todos 列表,而是分别存在两个独立 todos 列表。也就是 React Hooks 只提供状态处理方法,不会持久化状态。如果要真正实现一个 Redux 功能,也就是全局维持一个状态,任何组件 useReducer 都会访问到同一份数据,可以和 useContext 一起使用。大体思路是利用 useContext 共享一份数据,作为 Custom Hooks 的数据源。具体实现可以参考 redux-react-hook。利用 useEffect 代替一些生命周期在 useState 位置附近,可以使用 useEffect 处理副作用:useEffect(() => { const subscription = props.source.subscribe(); return () => { // Clean up the subscription subscription.unsubscribe(); }; });useEffect 的代码既会在初始化时候执行,也会在后续每次 rerender 时执行,而返回值在析构时执行。这个更多带来的是便利,对比一下 React 版 G2 调用流程:class Component extends React.PureComponent<Props, State> { private chart: G2.Chart = null; private rootDomRef: React.ReactInstance = null; componentDidMount() { this.rootDom = ReactDOM.findDOMNode(this.rootDomRef) as HTMLDivElement; this.chart = new G2.Chart({ container: document.getElementById("chart"), forceFit: true, height: 300 }); this.freshChart(this.props); } componentWillReceiveProps(nextProps: Props) { this.freshChart(nextProps); } componentWillUnmount() { this.chart.destroy(); } freshChart(props: Props) { // do something this.chart.render(); } render() { return <div ref={ref => (this.rootDomRef = ref)} />; } }用 React Hooks 可以这么做:function App() { const ref = React.useRef(null); let chart: G2.Chart = null; React.useEffect(() => { if (!chart) { chart = new G2.Chart({ container: ReactDOM.findDOMNode(ref.current) as HTMLDivElement, width: 500, height: 500 }); } // do something chart.render(); return () => chart.destroy(); }); return <div ref={ref} />; }可以看到将细碎的代码片段结合成了一个完整的代码块,更维护。现在介绍了 useState useContext useEffect useRef 等常用 hooks,更多可以查阅:内置 Hooks,相信不久的未来,这些 API 又会成为一套新的前端规范。React Hooks 将带来什么变化Hooks 带来的约定Hook 函数必须以 "use" 命名开头,因为这样才方便 eslint 做检查,防止用 condition 判断包裹 useHook 语句。为什么不能用 condition 包裹 useHook 语句,详情可以见 官方文档,这里简单介绍一下。React Hooks 并不是通过 Proxy 或者 getters 实现的(具体可以看这篇文章 React hooks: not magic, just arrays),而是通过数组实现的,每次 useState 都会改变下标,如果 useState 被包裹在 condition 中,那每次执行的下标就可能对不上,导致 useState 导出的 setter 更新错数据。虽然有 eslint-plugin-react-hooks 插件保驾护航,但这第一次将 “约定优先” 理念引入了 React 框架中,带来了前所未有的__代码命名和顺序限制__(函数命名遭到官方限制,JS 自由主义者也许会暴跳如雷),但带来的便利也是前所未有的(没有比 React Hooks 更好的状态共享方案了,约定带来提效,自由的代价就是回到 renderProps or HOC,各团队可以自行评估)。笔者认为,React Hooks 的诞生,也许来自于这个灵感:“不如通过增加一些约定,彻底解决状态共享问题吧!”React 约定大于配置脚手架 nextjs umi 以及笔者的 pri 都通过有 “约定路由” 的功能,大大降低了路由配置复杂度,__那么 React Hooks 就像代码级别的约定__,大大降低了代码复杂度。状态与 UI 的界限会越来越清晰因为 React Hooks 的特性,如果一个 Hook 不产生 UI,那么它可以永远被其他 Hook 封装,虽然允许有副作用,但是被包裹在 useEffect 里,总体来说还是挺函数式的。而 Hooks 要集中在 UI 函数顶部写,也很容易养成书写无状态 UI 组件的好习惯,践行 “状态与 UI 分开” 这个理念会更容易。不过这个理念稍微有点蹩脚的地方,那就是 “状态” 到底是什么。function App() { const [count, setCount] = useCount(); return <span>{count}</span>; }我们知道 useCount 算是无状态的,因为 React Hooks 本质就是 renderProps 或者 HOC 的另一种写法,换成 renderProps 就好理解了:<Count>{(count, setCount) => <App count={count} setCount={setCount} />}</Count>; function App(props) { return <span>{props.count}</span>; }可以看到 App 组件是无状态的,输出完全由输入(Props)决定。那么有状态无 UI 的组件就是 useCount 了:function useCount() { const [count, setCount] = useState(0); return [count, setCount]; }有状态的地方应该指 useState(0) 这句,不过这句和无状态 UI 组件 App 的 useCount() 很像,既然 React 把 useCount 成为自定义 Hook,那么 useState 就是官方 Hook,具有一样的定义,因此可以认为 useCount 是无状态的,useState 也是一层 renderProps,最终的状态其实是 useState 这个 React 内置的组件。我们看 renderProps 嵌套的表达:<UseState> {(count, setCount) => ( <UseCount> {" "} {/**虽然是透传,但给 count 做了去重,不可谓没有作用 */} {(count, setCount) => <App count={count} setCount={setCount} />} </UseCount> )} </UseState>能确定的是,App 一定有 UI,而上面两层父级组件一定没有 UI。为了最佳实践,我们尽量避免 App 自己维护状态,而其父级的 RenderProps 组件可以维护状态(也可以不维护状态,做个二传手)。因此可以考虑在 “有状态的组件没有渲染,有渲染的组件没有状态” 这句话后面加一句:没渲染的组件也可以没状态。React Hooks 实践通过上面的理解,你已经对 React Hooks 有了基本理解,也许你也看了 React Hooks 基本实现剖析(就是数组),但理解实现原理就可以用好了吗?学的是知识,而用的是技能,看别人的用法就像刷抖音一样(哇,饭还可以这样吃?),你总会有新的收获。首先,站在使用角度,要理解 React Hooks 的特点是 “非常方便的 Connect 一切”,所以无论是数据流、Network,或者是定时器都可以监听,有一点 RXJS 的意味,也就是你可以利用 React Hooks,将 React 组件打造成:任何事物的变化都是输入源,当这些源变化时会重新触发 React 组件的 render,你只需要挑选组件绑定哪些数据源(use 哪些 Hooks),然后只管写 render 函数就行了!DOM 副作用修改 / 监听做一个网页,总有一些看上去和组件关系不大的麻烦事,比如修改页面标题(切换页面记得改成默认标题)、监听页面大小变化(组件销毁记得取消监听)、断网时提示(一层层装饰器要堆成小山了)。而 React Hooks 特别擅长做这些事,造这种轮子,大小皆宜。由于 React Hooks 降低了高阶组件使用成本,那么一套生命周期才能完成的 “杂耍” 将变得非常简单。下面举几个例子:修改页面 title效果:在组件里调用 useDocumentTitle 函数即可设置页面标题,且切换页面时,页面标题重置为默认标题 “前端精读”。useDocumentTitle("个人中心");实现:直接用 document.title 赋值,不能再简单。在销毁时再次给一个默认标题即可,这个简单的函数可以抽象在项目工具函数里,每个页面组件都需要调用。function useDocumentTitle(title) { useEffect( () => { document.title = title; return () => (document.title = "前端精读"); }, [title] ); }在线 Demo监听页面大小变化,网络是否断开效果:在组件调用 useWindowSize 时,可以拿到页面大小,并且在浏览器缩放时自动触发组件更新。const windowSize = useWindowSize(); return <div>页面高度:{windowSize.innerWidth}</div> ;实现:和标题思路基本一致,这次从 window.innerHeight 等 API 直接拿到页面宽高即可,注意此时可以用 window.addEventListener('resize') 监听页面大小变化,此时调用 setValue 将会触发调用自身的 UI 组件 rerender,就是这么简单!最后注意在销毁时,removeEventListener 注销监听。function getSize() { return { innerHeight: window.innerHeight, innerWidth: window.innerWidth, outerHeight: window.outerHeight, outerWidth: window.outerWidth }; } function useWindowSize() { let [windowSize, setWindowSize] = useState(getSize()); function handleResize() { setWindowSize(getSize()); } useEffect(() => { window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); }; }, []); return windowSize; }在线 Demo动态注入 css效果:在页面注入一段 class,并且当组件销毁时,移除这个 class。const className = useCss({ color: "red" }); return <div className={className}>Text.</div> ;实现:可以看到,Hooks 方便的地方是在组件销毁时移除副作用,所以我们可以安心的利用 Hooks 做一些副作用。注入 css 自然不必说了,而销毁 css 只要找到注入的那段引用进行销毁即可,具体可以看这个 代码片段。DOM 副作用修改 / 监听场景有一些现成的库了,从名字上就能看出来用法:document-visibility、network-status、online-status、window-scroll-position、window-size、document-title。组件辅助Hooks 还可以增强组件能力,比如拿到并监听组件运行时宽高等。获取组件宽高效果:通过调用 useComponentSize 拿到某个组件 ref 实例的宽高,并且在宽高变化时,rerender 并拿到最新的宽高。const ref = useRef(null); let componentSize = useComponentSize(ref); return ( <> {componentSize.width} <textArea ref={ref} /> </> );实现:和 DOM 监听类似,这次换成了利用 ResizeObserver 对组件 ref 进行监听,同时在组件销毁时,销毁监听。其本质还是监听一些副作用,但通过 ref 的传递,我们可以对组件粒度进行监听和操作了。useLayoutEffect(() => { handleResize(); let resizeObserver = new ResizeObserver(() => handleResize()); resizeObserver.observe(ref.current); return () => { resizeObserver.disconnect(ref.current); resizeObserver = null; }; }, []);在线 Demo,对应组件 component-size。拿到组件 onChange 抛出的值效果:通过 useInputValue() 拿到 Input 框当前用户输入的值,而不是手动监听 onChange 再腾一个 otherInputValue 和一个回调函数把这一堆逻辑写在无关的地方。let name = useInputValue("Jamie"); // name = { value: 'Jamie', onChange: [Function] } return <input {...name} />;可以看到,这样不仅没有占用组件自己的 state,也不需要手写 onChange 回调函数进行处理,这些处理都压缩成了一行 use hook。实现:读到这里应该大致可以猜到了,利用 useState 存储组件的值,并抛出 value 与 onChange,监听 onChange 并通过 setValue 修改 value, 就可以在每次 onChange 时触发调用组件的 rerender 了。function useInputValue(initialValue) { let [value, setValue] = useState(initialValue); let onChange = useCallback(function(event) { setValue(event.currentTarget.value); }, []); return { value, onChange }; }这里要注意的是,我们对组件增强时,__组件的回调一般不需要销毁监听,而且仅需监听一次,这与 DOM 监听不同__,因此大部分场景,我们需要利用 useCallback 包裹,并传一个空数组,来保证永远只监听一次,而且不需要在组件销毁时注销这个 callback。在线 Demo,对应组件 input-value。做动画利用 React Hooks 做动画,一般是拿到一些具有弹性变化的值,我们可以将值赋给进度条之类的组件,这样其进度变化就符合某种动画曲线。在某个时间段内获取 0-1 之间的值这个是动画最基本的概念,某个时间内拿到一个线性增长的值。效果:通过 useRaf(t) 拿到 t 毫秒内不断刷新的 0-1 之间的数字,期间组件会不断刷新,但刷新频率由 requestAnimationFrame 控制(不会卡顿 UI)。const value = useRaf(1000);实现:写起来比较冗长,这里简单描述一下。利用 requestAnimationFrame 在给定时间内给出 0-1 之间的值,那每次刷新时,只要判断当前刷新的时间点占总时间的比例是多少,然后做分母,分子是 1 即可。在线 Demo,对应组件 use-raf。弹性动画效果:通过 useSpring 拿到动画值,组件以固定频率刷新,而这个动画值以弹性函数进行增减。实际调用方式一般是,先通过 useState 拿到一个值,再通过动画函数包住这个值,这样组件就会从原本的刷新一次,变成刷新 N 次,拿到的值也随着动画函数的规则变化,最后这个值会稳定到最终的输入值(如例子中的 50)。const [target, setTarget] = useState(50); const value = useSpring(target); return <div onClick={() => setTarget(100)}>{value}</div> ;实现:为了实现动画效果,需要依赖 rebound 库,它可以实现将一个目标值拆解为符合弹性动画函数过程的功能,那我们需要利用 React Hooks 做的就是在第一次接收到目标值是,调用 spring.setEndValue 来触发动画事件,并在 useEffect 里做一次性监听,再值变时重新 setValue 即可。最神奇的 setTarget 联动 useSpring 重新计算弹性动画部分,是通过 useEffect 第二个参数实现的:useEffect( () => { if (spring) { spring.setEndValue(targetValue); } }, [targetValue] );也就是当目标值变化后,才会进行新的一轮 rerender,所以 useSpring 并不需要监听调用处的 setTarget,它只需要监听 target 的变化即可,而巧妙利用 useEffect 的第二个参数可以事半功倍。在线 DemoTween 动画明白了弹性动画原理,Tween 动画就更简单了。效果:通过 useTween 拿到一个从 0 变化到 1 的值,这个值的动画曲线是 tween。可以看到,由于取值范围是固定的,所以我们不需要给初始值了。const value = useTween();实现:通过 useRaf 拿到一个线性增长的值(区间也是 0 ~ 1),再通过 easing 库将其映射到 0 ~ 1 到值即可。这里用到了 hook 调用 hook 的联动(通过 useRaf 驱动 useTween),还可以在其他地方举一反三。const fn: Easing = easing[easingName]; const t = useRaf(ms, delay); return fn(t);发请求利用 Hooks,可以将任意请求 Promise 封装为带有标准状态的对象:loading、error、result。通用 Http 封装效果:通过 useAsync 将一个 Promise 拆解为 loading、error、result 三个对象。const { loading, error, result } = useAsync(fetchUser, [id]);实现:在 Promise 的初期设置 loading,结束后设置 result,如果出错则设置 error,这里可以将请求对象包装成 useAsyncState 来处理,这里就不放出来了。export function useAsync(asyncFunction) { const asyncState = useAsyncState(options); useEffect(() => { const promise = asyncFunction(); asyncState.setLoading(); promise.then( result => asyncState.setResult(result);, error => asyncState.setError(error); ); }, params); }具体代码可以参考 react-async-hook,这个功能建议仅了解原理,具体实现因为有一些边界情况需要考虑,比如组件 isMounted 后才能相应请求结果。Request Service业务层一般会抽象一个 request service 做统一取数的抽象(比如统一 url,或者可以统一换 socket 实现等等)。假如以前比较 low 的做法是:async componentDidMount() { // setState: 改 isLoading state try { const data = await fetchUser() // setState: 改 isLoading、error、data } catch (error) { // setState: 改 isLoading、error } }后来把请求放在 redux 里,通过 connect 注入的方式会稍微有些改观:@Connect(...) class App extends React.PureComponent { public componentDidMount() { this.props.fetchUser() } public render() { // this.props.userData.isLoading | error | data } }最后会发现还是 Hooks 简洁明了:function App() { const { isLoading, error, data } = useFetchUser(); }而 useFetchUser 利用上面封装的 useAsync 可以很容易编写:const fetchUser = id => fetch(`xxx`).then(result => { if (result.status !== 200) { throw new Error("bad status = " + result.status); } return result.json(); }); function useFetchUser(id) { const asyncFetchUser = useAsync(fetchUser, id); return asyncUser; }填表单React Hooks 特别适合做表单,尤其是 antd form 如果支持 Hooks 版,那用起来会方便许多:function App() { const { getFieldDecorator } = useAntdForm(); return ( <Form onSubmit={this.handleSubmit} className="login-form"> <FormItem> {getFieldDecorator("userName", { rules: [{ required: true, message: "Please input your username!" }] })( <Input prefix={<Icon type="user" style={{ color: "rgba(0,0,0,.25)" }} />} placeholder="Username" /> )} </FormItem> <FormItem> <Button type="primary" htmlType="submit" className="login-form-button"> Log in </Button> Or <a href="">register now!</a> </FormItem> </Form> ); }不过虽然如此,getFieldDecorator 还是基于 RenderProps 思路的,彻底的 Hooks 思路是利用之前说的 __组件辅助方式,提供一个组件方法集,用解构方式传给组件__。Hooks 思维的表单组件效果:通过 useFormState 拿到表单值,并且提供一系列 __组件辅助__ 方法控制组件状态。const [formState, { text, password }] = useFormState(); return ( <form> <input {...text("username")} required /> <input {...password("password")} required minLength={8} /> </form> );上面可以通过 formState 随时拿到表单值,和一些校验信息,通过 password("pwd") 传给 input 组件,让这个组件达到受控状态,且输入类型是 password 类型,表单 key 是 pwd。而且可以看到使用的 form 是原生标签,这种表单增强是相当解耦的。实现:仔细观察一下结构,不难发现,我们只要结合 组件辅助 小节说的 “拿到组件 onChange 抛出的值” 一节的思路,就能轻松理解 text、password 是如何作用于 input 组件,并拿到其输入状态。往简单的来说,只要把这些状态 Merge 起来,通过 useReducer 聚合到 formState 就可以实现了。为了简化,我们只考虑对 input 的增强,源码仅需 30 几行:export function useFormState(initialState) { const [state, setState] = useReducer(stateReducer, initialState || {}); const createPropsGetter = type => (name, ownValue) => { const hasOwnValue = !!ownValue; const hasValueInState = state[name] !== undefined; function setInitialValue() { let value = ""; setState({ [name]: value }); } const inputProps = { name, // 给 input 添加 type: text or password get value() { if (!hasValueInState) { setInitialValue(); // 给初始化值 } return hasValueInState ? state[name] : ""; // 赋值 }, onChange(e) { let { value } = e.target; setState({ [name]: value }); // 修改对应 Key 的值 } }; return inputProps; }; const inputPropsCreators = ["text", "password"].reduce( (methods, type) => ({ ...methods, [type]: createPropsGetter(type) }), {} ); return [ { values: state }, // formState inputPropsCreators ]; }上面 30 行代码实现了对 input 标签类型的设置,监听 value onChange,最终聚合到大的 values 作为 formState 返回。读到这里应该发现对 React Hooks 的应用都是万变不离其宗的,特别是对组件信息的获取,通过解构方式来做,Hooks 内部再做一下聚合,就完成表单组件基本功能了。实际上一个完整的轮子还需要考虑 checkbox radio 的兼容,以及校验问题,这些思路大同小异,具体源码可以看 react-use-form-state。模拟生命周期有的时候 React15 的 API 还是挺有用的,利用 React Hooks 几乎可以模拟出全套。componentDidMount效果:通过 useMount 拿到 mount 周期才执行的回调函数。useMount(() => { // quite similar to `componentDidMount` });实现:componentDidMount 等价于 useEffect 的回调(仅执行一次时),因此直接把回调函数抛出来即可。useEffect(() => void fn(), []);componentWillUnmount效果:通过 useUnmount 拿到 unmount 周期才执行的回调函数。useUnmount(() => { // quite similar to `componentWillUnmount` });实现:componentWillUnmount 等价于 useEffect 的回调函数返回值(仅执行一次时),因此直接把回调函数返回值抛出来即可。useEffect(() => fn, []);componentDidUpdate效果:通过 useUpdate 拿到 didUpdate 周期才执行的回调函数。useUpdate(() => { // quite similar to `componentDidUpdate` });实现:componentDidUpdate 等价于 useMount 的逻辑每次执行,除了初始化第一次。因此采用 mouting flag(判断初始状态)+ 不加限制参数确保每次 rerender 都会执行即可。const mounting = useRef(true); useEffect(() => { if (mounting.current) { mounting.current = false; } else { fn(); } });Force Update效果:这个最有意思了,我希望拿到一个函数 update,每次调用就强制刷新当前组件。const update = useUpdate();实现:我们知道 useState 下标为 1 的项是用来更新数据的,而且就算数据没有变化,调用了也会刷新组件,所以我们可以把返回一个没有修改数值的 setValue,这样它的功能就仅剩下刷新组件了。const useUpdate = () => useState(0)[1];对于 getSnapshotBeforeUpdate, getDerivedStateFromError, componentDidCatch 目前 Hooks 是无法模拟的。isMounted很久以前 React 是提供过这个 API 的,后来移除了,原因是可以通过 componentWillMount 和 componentWillUnmount 推导。自从有了 React Hooks,支持 isMount 简直是分分钟的事。效果:通过 useIsMounted 拿到 isMounted 状态。const isMounted = useIsMounted();实现:看到这里的话,应该已经很熟悉这个套路了,useEffect 第一次调用时赋值为 true,组件销毁时返回 false,注意这里可以加第二个参数为空数组来优化性能。const [isMount, setIsMount] = useState(false); useEffect(() => { if (!isMount) { setIsMount(true); } return () => setIsMount(false); }, []); return isMount;在线 Demo存数据上一篇提到过 React Hooks 内置的 useReducer 可以模拟 Redux 的 reducer 行为,那唯一需要补充的就是将数据持久化。我们考虑最小实现,也就是全局 Store + Provider 部分。全局 Store效果:通过 createStore 创建一个全局 Store,再通过 StoreProvider 将 store 注入到子组件的 context中,最终通过两个 Hooks 进行获取与操作:useStore 与 useAction:const store = createStore({ user: { name: "小明", setName: (state, payload) => { state.name = payload; } } }); const App = () => ( <StoreProvider store={store}> <YourApp /> </StoreProvider> ); function YourApp() { const userName = useStore(state => state.user.name); const setName = userAction(dispatch => dispatch.user.setName); }实现:这个例子的实现可以单独拎出一篇文章了,所以笔者从存数据的角度剖析一下 StoreProvider 的实现。对,Hooks 并不解决 Provider 的问题,所以全局状态必须有 Provider,但这个 Provider 可以利用 React 内置的 createContext 简单搞定:const StoreContext = createContext(); const StoreProvider = ({ children, store }) => ( <StoreContext.Provider value={store}>{children}</StoreContext.Provider> );剩下就是 useStore 怎么取到持久化 Store 的问题了,这里利用 useContext 和刚才创建的 Context 对象:const store = useContext(StoreContext); return store;更多源码可以参考 easy-peasy,这个库基于 redux 编写,提供了一套 Hooks API。封装原有库是不是 React Hooks 出现后,所有的库都要重写一次?当然不是,我们看看其他库如何做改造。RenderProps to Hooks这里拿 react-powerplug 举例。比如有一个 renderProps 库,希望改造成 Hooks 的用法:import { Toggle } from 'react-powerplug' function App() { return ( <Toggle initial={true}> {({ on, toggle }) => ( <Checkbox checked={on} onChange={toggle} /> )} </Toggle> ) } ↓ ↓ ↓ ↓ ↓ ↓ import { useToggle } from 'react-powerhooks' function App() { const [on, toggle] = useToggle() return <Checkbox checked={on} onChange={toggle} /> }效果:假如我是 react-powerplug 的维护者,怎么样最小成本支持 React Hook? 说实话这个没办法一步做到,但可以通过两步实现。export function Toggle() { // 这是 Toggle 的源码 // balabalabala.. } const App = wrap(() => { // 第一步:包 wrap const [on, toggle] = useRenderProps(Toggle); // 第二步:包 useRenderProps });实现:首先解释一下为什么要包两层,首先 Hooks 必须遵循 React 的规范,我们必须写一个 useRenderProps 函数以符合 Hooks 的格式,**那问题是如何拿到 Toggle 给 render 的 on 与 toggle?**正常方式应该拿不到,所以退而求其次,将 useRenderProps 拿到的 Toggle 传给 wrap,让 wrap 构造 RenderProps 执行环境拿到 on 与 toggle 后,调用 useRenderProps 内部的 setArgs 函数,让 const [on, toggle] = useRenderProps(Toggle) 实现曲线救国。const wrappers = []; // 全局存储 wrappers export const useRenderProps = (WrapperComponent, wrapperProps) => { const [args, setArgs] = useState([]); const ref = useRef({}); if (!ref.current.initialized) { wrappers.push({ WrapperComponent, wrapperProps, setArgs }); } useEffect(() => { ref.current.initialized = true; }, []); return args; // 通过下面 wrap 调用 setArgs 获取值。 };由于 useRenderProps 会先于 wrap 执行,所以 wrappers 会先拿到 Toggle,wrap 执行时直接调用 wrappers.pop() 即可拿到 Toggle 对象。然后构造出 RenderProps 的执行环境即可:export const wrap = FunctionComponent => props => { const element = FunctionComponent(props); const ref = useRef({ wrapper: wrappers.pop() }); // 拿到 useRenderProps 提供的 Toggle const { WrapperComponent, wrapperProps } = ref.current.wrapper; return createElement(WrapperComponent, wrapperProps, (...args) => { // WrapperComponent => Toggle,这一步是在构造 RenderProps 执行环境 if (!ref.current.processed) { ref.current.wrapper.setArgs(args); // 拿到 on、toggle 后,通过 setArgs 传给上面的 args。 ref.current.processed = true; } else { ref.current.processed = false; } return element; }); };以上实现方案参考 react-hooks-render-props,有需求要可以拿过来直接用,不过实现思路可以参考,作者的脑洞挺大。Hooks to RenderProps好吧,如果希望 Hooks 支持 RenderProps,那一定是希望同时支持这两套语法。效果:一套代码同时支持 Hooks 和 RenderProps。实现:其实 Hooks 封装为 RenderProps 最方便,因此我们使用 Hooks 写核心的代码,假设我们写一个最简单的 Toggle:const useToggle = initialValue => { const [on, setOn] = useState(initialValue); return { on, toggle: () => setOn(!on) }; };在线 Demo然后通过 render-props 这个库可以轻松封装出 RenderProps 组件:const Toggle = ({ initialValue, children, render = children }) => renderProps(render, useToggle(initialValue));在线 Demo其实 renderProps 这个组件的第二个参数,在 Class 形式 React 组件时,接收的是 this.state,现在我们改成 useToggle 返回的对象,也可以理解为 state,利用 Hooks 机制驱动 Toggle 组件 rerender,从而让子组件 rerender。封装原本对 setState 增强的库Hooks 也特别适合封装原本就作用于 setState 的库,比如 immer。useState 虽然不是 setState,但却可以理解为控制高阶组件的 setState,我们完全可以封装一个自定义的 useState,然后内置对 setState 的优化。比如 immer 的语法是通过 produce 包装,将 mutable 代码通过 Proxy 代理为 immutable:const nextState = produce(baseState, draftState => { draftState.push({ todo: "Tweet about it" }); draftState[1].done = true; });那这个 produce 就可以通过封装一个 useImmer 来隐藏掉:function useImmer(initialValue) { const [val, updateValue] = React.useState(initialValue); return [ val, updater => { updateValue(produce(updater)); } ]; }使用方式:const [value, setValue] = useImmer({ a: 1 }); value(obj => (obj.a = 2)); // immutable总结把 React Hooks 当作更便捷的 RenderProps 去用吧,虽然写法看上去是内部维护了一个状态,但其实等价于注入、Connect、HOC、或者 renderProps,那么如此一来,使用 renderProps 的门槛会大大降低,因为 Hooks 用起来实在是太方便了,我们可以抽象大量 Custom Hooks,让代码更加 FP,同时也不会增加嵌套层级。那么看了这么多使用实例,你准备怎么用呢?原文:https://github.com/ascoders/blog/issues/29
2019年01月12日
809 阅读
0 评论
0 点赞
2019-01-01
小程序全局组件的使用
开发小程序时经常会用到全局组件,不限制页面随时可用的组件,但小程序的全局组件非常鸡肋,小程序并不能动态渲染,需要每个页面都需要引入,虽然可以全局声明 (开发者工具 1.02.1810190 支持),但是像全局提醒组件还是需要每个页面写组件,但是可以把组件方法提到全局 也是很好了 ,接下来开始小程序全局组件的使用吧!编写全局提示组件index.wxml<view class="notice-container {{visible?'notice-show':''}}"> {{ item.content }} </view>index.wxss.notice-container { position: fixed; top: 0; min-height: 30px; line-height: 30px; width: 100%; background: rgba(0, 0, 0, 0.25); color: #fff; display: none; } .notice-container .notice-show { display: block; }index.jsconst default_data = { visible: false, content: "" }; let timmer = null; Component({ data: { ...default_data }, methods: { handleShow(options) { this.setData({ ...options, visible: true }); const d = 2 * 1000; if (timmer) clearTimeout(timmer); if (d !== 0) { timmer = setTimeout(() => { this.handleHide(); timmer = null; }, d); } }, handleHide() { this.setData({ ...default_data }); } } });index.json{ "component": true }全局声明在app.json配置中添加如下配置{ "usingComponents": { "notice": "./components/notice/index" } }页面引入这就麻烦了 需要的页面都需要引入只是不需要声明了页面引入如下<notice id="notice"></notice>这里的id 值很重要后面要用到全局调用接下来很重要新建notice.jsfunction getCtx(selector) { const pages = getCurrentPages(); const ctx = pages[pages.length - 1]; const componentCtx = ctx.selectComponent(selector); if (!componentCtx) { console.error("无法找到对应的组件"); return null; } return componentCtx; } function Notice(options) { const { selector = "#notice" } = options; // notice 为刚才的id值 const ctx = getCtx(selector); ctx.handleShow(options); } const $notice = Notice; export default $notice;每个页面全局调用这个 $notice 方法就可以了最后反正小程序的全局组件还是很难用,如果能 js 动态渲染就好了 期待吧。。
2019年01月01日
2,720 阅读
0 评论
2 点赞
2018-06-25
Redux介绍之React-Redux
我们已经详细介绍了 Action,Reducer,Store 和它们之间的流转关系,Redux 的基础知识差不多也介绍完了,但 Redux 不是 React 专用,它也可以支持其他框架,本篇介绍一下如何在 React 里使用 Redux!清单Providerconnect 之 mapStateToPropsconnect 之 mapDispatchToPropsconnect 之 mergeProps实现原理先要安装一下 react-redux 包: yarn add –D react-redux根据官网推荐将 React 组件分为容器组件 container 和展示组件 component。为了使代码结构更加合理,我们如下图,在项目根目录里新建 container 和 component 目录。container 目录里的组件需要关心 Redux。而 component 目录里的组件仅做展示用,不需要关心 Redux。这是一种最佳实践,并没有语法上的强制规定,因此 component 目录的组件绑定 Redux 也没问题,但最佳实践还是遵守比较好,否则业务代码会比较混乱。components 目录下放两个供展示用的 alert 和 number 组件,这两个组件完全不会感知到 Redux 的存在,它们依赖传入的 props 变化,来触发自身的 render 方法。本系列不是 React 教程,React 组件的代码请自行参照源码。containers 目录下的 sample 组件会关联 Redux,更新完的数据作为 alert 和 number 组件的 props 传递给它们。组件都被抽出后,原本 entries 目录下的文件中还剩下什么呢?entries/reactRedux.js: import { Provider } from 'react-redux'; // 引入 react-redux …… render( <Provider store={store}> <Sample /> </Provider>, document.getElementById('app'), );react-redux 包一共就两个 API:和 connect 方法。在 React 框架下使用 Redux 的第一步就是将入口组件包进里,store 指定通过 createStore 生成出来的 Store。只有这样,被包进的组件及子组件才能访问到 Store,才能使用 connect 方法。入口解决了,我们看一下 sample 组件是如何用 connect 方法关联 Redux 的。先看一下 connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])方法,签名有点长,参照 containers/sample/sample.js:const mapStateToProps = state => { return { number: state.changeNumber.number, showAlert: state.toggleAlert.showAlert }; }; const mapDispatchToProps = { incrementNum: action.number.incrementNum, decrementNum: action.number.decrementNum, clearNum: action.number.clearNum, toggleAlert: action.alert.toggleAlert }; export default connect( mapStateToProps, mapDispatchToProps )(Sample);connect 之 mapStateToPropsconnect 的第一个参数 mapStateToProps 是一个 function:[mapStateToProps(state, [ownProps]): stateProps],作用是负责输入,将 Store 里的 state 变成组件的 props。函数返回值是一个 key-value 的 plain object。例子代码里是:const mapStateToProps = state => { return { number: state.changeNumber.number, showAlert: state.toggleAlert.showAlert }; };函数返回值是一个将 state 和组件 props 建立了映射关系的 plain object。你可以这样理解:connect 的第一个参数 mapStateToProps 就是输入。将 state 绑定到组件的 props 上。这样会自动 Store.subscribe 组件。当建立了映射关系的 state 更新时,会调用 mapStateToProps 同步更新组件的 props 值,触发组件的 render 方法。如果 mapStateToProps 为空(即设成()=>({})),那 Store 里的任何更新就不会触发组件的 render 方法。mapStateToProps 方法还支持第二个可选参数 ownProps,看名字就知道是组件自己原始的 props(即不包含 connect 后新增的 props)。例子代码因为比较简单,没有用到 ownProps。可以 YY 一个例子:const mapStateToProps = (state, ownProps) => { // state 是 {userList: [{id: 0, name: 'Jack'}, ...]} return { isMe: state.userList.includes({ id: ownProps.userId }) }; };当 state 或 ownProps 更新时,mapStateToProps 都会被调用,更新组件的 props 值。connect 之 mapDispatchToPropsconnect 的第二个参数 mapDispatchToProps 可以是一个 object 也可以是一个 function,作用是负责输出,将 Action creator 绑定到组件的 props 上,这样组件就能派发 Action,更新 state 了。当它为 object 时,应该是一个 key-value 的 plain object,key 是组件 props,value 是一个 Action creator。例子代码里就采用了这个方式:const mapDispatchToProps = { incrementNum: action.number.incrementNum, decrementNum: action.number.decrementNum, clearNum: action.number.clearNum, toggleAlert: action.alert.toggleAlert };将定义好的 Action creator 映射成组件的 porps,这样就能在组件中通过this.props. incrementNum()方式来 dispatch Action 出去,通知 Reducer 修改 state。如果你对 Action 比较熟悉的话,可能会疑惑,this.props.incrementNum()只是生成了一个 Action,应该是写成:dispatch(this.props. incrementNum())才对吧?继续看下面介绍的 function 形式的 mapDispatchToProps 就能明白,其实 dispatch 已经被 connect 封装进去了,因此你不必手动写 dispatch 了。mapDispatchToProps 还可以是一个 function:[mapDispatchToProps(dispatch, [ownProps]): dispatchProps]。改写例子代码:import { bindActionCreators } from "redux"; const mapDispatchToProps2 = (dispatch, ownProps) => { return { incrementNum: bindActionCreators(action.number.incrementNum, dispatch), decrementNum: bindActionCreators(action.number.decrementNum, dispatch), clearNum: bindActionCreators(action.number.clearNum, dispatch), toggleAlert: bindActionCreators(action.alert.toggleAlert, dispatch) }; };这段代码和例子代码中的 object 形式的 mapDispatchToProps 是等价的。世上并没有自动的事,所谓的自动只不过是 connet 中封装了 Store.dispatch 而已。第一个参数是 dispatch,第二个可选参数 ownProps 和 mapStateToProps 里作用是一样的,不赘述。connect 之 mergeProps我们现在已经知道,经过 conncet 的组件的 props 有 3 个来源:一是由 mapStateToProps 将 state 映射成的 props,二是由 mapDispatchToProps 将 Action creator 映射成的 props,三是组件自身的 props。connect 的第三个参数 mergeProps 也是一个 function:[mergeProps(stateProps, dispatchProps, ownProps): props],参数分别对应了上述 props 的 3 个来源,作用是整合这些 props。例如过滤掉不需要的 props:const mergeProps = (stateProps, dispatchProps, ownProps) => { return { ...ownProps, ...stateProps, incrementNum: dispatchProps.incrementNum // 只输出incrementNum }; }; export default connect( mapStateToProps, mapDispatchToProps, mergeProps )(Sample);这样你组件里就无法从 props 里取到 decrementNum 和 clearNum 了。再例如重新组织 props:const mergeProps = (stateProps, dispatchProps, ownProps) => { return { ...ownProps, state: stateProps, actions: { ...dispatchProps, ...ownProps.actions } }; }; export default connect( mapStateToProps, mapDispatchToProps, mergeProps )(Sample);这样你代码里无法this.props.incrementNum()这样调用,要改成this.props.actions.incrementNum()这样调用。至此 react-redux 的内容就介绍完了,一共就两个 API:用于在入口处包裹需要用到 Redux 的组件。conncet 方法用于将组件绑定 Redux。第一个参数负责输入,将 state 映射成组件 props。第二个参数负责输出,允许组件去改变 state 的值。第三个参数甚至都没什么出镜率,例子代码就没有用到这个参数,可以让程序员自己调整组件的 props。实现原理接下来介绍一下 react-redux 的实现原理,需要一定 React 基础,如果你能看懂相必是极好的。但如果你只想使用 react-redux 的话,上述内容就足够了,下面的部分看不懂也没关系。我们知道 React 里有个全局变量 context,它其实和 React 一切皆组件的设计思路不符。但实际开发中,组件间嵌套层次比较深时,传递数据真的是比较麻烦。基于此,React 提供了个类似后门的全局变量 context。可用将组件间共享的数据放到 contex 里,这样做的优点是:所有组件都可以随时访问到 context 里共享的值,免去了数据层层传递的麻烦,非常方便。缺点是:和所有其他语言一样,全局变量意味着所有人都可以随意修改它,导致不可控。Redux 恰好需要一个全局的 Store,那在 React 框架里,将 Store 存入 context 中再合适不过了,所有组件都能随时访问到 context 里的 Store。而且 Redux 规定了只能通过 dispatch Action 来修改 Store 里的数据,因此规避了所有人都可以随意修改 context 值的缺点。完美。理解了这层,再回头看,它的作用是将 createStore 生成的 store 保存进 context。这样被它包裹着的子组件都可以访问到 context 里的 Store。import React, { Component } from "react"; import PropTypes from "prop-types"; export default class Provider extends Component { static contextTypes = { store: PropTypes.object, children: PropTypes.any }; static childContextTypes = { store: PropTypes.object }; getChildContext = () => { return { store: this.props.store }; }; render() { return <div>{this.props.children}</div> ; } }经过 conncet 后的组件是一个 HOC 高阶组件(High-order Component),参照React.js 小书的图,一图胜千言:HOC 高阶组件听上去名字比较吓人,不像人话,我第一次听到的反映也是“什么鬼?”。但其实原理不复杂,说穿了就是为了消除重复代码用的。有些代码每个组件都要重复写(例如 getChildContext),干脆将它们抽取出来写到一个组件内,这个组件就是高阶组件。高阶组件内部的包装组件和被包装组件之间通过 props 传递数据。即让 connect 和 context 打交道,然后通过 props 把参数传给组件自身。我们来实现一下 connect。第一步:内部封装掉了每个组件都要写的访问 context 的代码:import React, { Component } from "react"; import PropTypes from "prop-types"; const connect = WrappedComponent => { class Connect extends Component { static contextTypes = { store: PropTypes.object }; render() { return <WrappedComponent />; } } return Connect; }; export default connect;第二步:封装掉 subscribe,当 store 变化,刷新组件的 props,触发组件的 render 方法 const connect = (WrappedComponent) => { class Connect extends Component { ... constructor() { super(); this.state = { allProps: {} } } componentWillMount() { const { store } = this.context; this._updateProps(); store.subscribe(this._updateProps); } _updateProps = () => { this.setState({ allProps: { // TBD ...this.props, } }); }; render () { return <WrappedComponent {...this.state.allProps} /> } } return Connect; };第三步:参数 mapStateToProps 封装掉组件从 context 中取 Store 的代码 export const connect = (mapStateToProps) => (WrappedComponent) => { class Connect extends Component { ... _updateProps () { const { store } = this.context let stateProps = mapStateToProps(store.getState()); this.setState({ allProps: { ...stateProps, ...this.props } }) } ... } return Connect }第四步:参数 mapDispatchToProps 封装掉组件往 context 里更新 Store 的代码 export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => { class Connect extends Component { ... _updateProps () { const { store } = this.context let stateProps = mapStateToProps(store.getState()); let dispatchProps = mapDispatchToProps(store.dispatch); this.setState({ allProps: { ...stateProps, ...dispatchProps, ...this.props } }) } ... } return Connect }完整版:import React, { Component } from "react"; import PropTypes from "prop-types"; const connect = (mapStateToProps, mapDispatchToProps) => WrappedComponent => { class Connect extends Component { static contextTypes = { store: PropTypes.object }; constructor() { super(); this.state = { allProps: {} }; } componentWillMount() { const { store } = this.context; this._updateProps(); store.subscribe(this._updateProps); } _updateProps = () => { const { store } = this.context; let stateProps = mapStateToProps(store.getState()); let dispatchProps = mapDispatchToProps(store.dispatch); this.setState({ allProps: { ...stateProps, ...dispatchProps, ...this.props } }); }; render() { return <WrappedComponent {...this.state.allProps} />; } } return Connect; }; export default connect;明白了原理后,再次总结一下 react-redux:用于在入口处包裹需要用到 Redux 的组件。本质上是将 store 放入 context 里。conncet 方法用于将组件绑定 Redux。本质上是 HOC,封装掉了每个组件都要写的板式代码。react-redux 的高封装性让开发者感知不到 context 的存在,甚至感知不到 Store 的 getState,subscribe,dispatch 的存在。只要 connect 一下,数据一变就自动刷新 React 组件,非常方便。后记源码已上传Github,请参照src/reactRedux文件夹。
2018年06月25日
477 阅读
0 评论
0 点赞
2018-06-25
Flex布局兼容国内浏览器的双核模式
flex 布局不支持低版本 ie 内核是确定了,没办法的改变的事实,但是国产浏览器采用哪一个内核打开你的页面,这个是你可以控制的。在 html 的<head>标签中加入如下代码:<meta name="renderer" content="webkit">然后浏览器打开你的页面时,会默认采用 chrome 内核来渲染页面,这是 360 浏览器的功能,其他国产浏览器也支持。但是用户非要点击“兼容模式”怎么办?我们还可以控制浏览器调用的 ie 渲染引擎的版本。在 html 的标签中加入如下代码:<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">这个指令不要被 edge 这个单词迷惑,他指的不是 edge 浏览器。而是告诉 ie 渲染引擎,使用最新内核,且采用标准模式渲染页面。chrome 这个指令也建议保留,他是让 IE 用户打开你的网页时,尝试调用 chrome 框架插件的。这是一个很老的 ie 插件,google 出品,让 ie 支持 chrome 内核。最后这两个指令都添加完毕之后,能实现这样的效果,国产浏览器打开你的网页时,优先调用 chrome 内核渲染。如果用户强制点击,选择兼容模式,会调用操作系统内安装的 IE 的标准模式渲染你的页面。如果用户的操作系统内安装了 ie9 或以上,则正常显示 flex。如果用户使用 ie 打开你的页面,当 ie 版本是 9 以上,或者系统内安装了 chrome 框架插件,flex 也能正常工作。所以以防万一 尽量 html<head>标签中都加入如下代码<meta name="renderer" content="webkit"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
2018年06月25日
2,388 阅读
0 评论
0 点赞
2018-06-20
Redux流程讲解
随着应用程序单页面需求的越来越复杂,应用状态的管理也变得越来越混乱,而 Redux 的就是为解决这一问题而出现的。在一个大型的应用程序中,应用的状态不仅包括从服务器获取的数据,还包括本地创建的数据,以及反应本地 UI 状态的数据,而 Redux 正是为解决这一复杂问题而存在的。redux 作为一种单向数据流的实现,配合 react 非常好用,尤其是在项目比较大,逻辑比较复杂的时候,单项数据流的思想能使数据的流向、变化都能得到清晰的控制,并且能很好的划分业务逻辑和视图逻辑。下图是 redux 的基本运作的流程。如上图所示,该图展示了 Redux 框架数据的基本工作流程。简单来说,首先由 view dispatch 拦截 action,然后执行对应 reducer 并更新到 store 中,最终 views 会根据 store 数据的改变执行界面的刷新渲染操作。同时,作为一款应用状态管理框架,为了让应用的状态管理不再错综复杂,使用 Redux 时应遵循三大基本原则,否则应用程序很容易出现难以察觉的问题。这三大原则包括: • 单一数据源 整个应用的 State 被存储在一个状态树中,且只存在于唯一的 Store 中。 • State 是只读的 对于 Redux 来说,任何时候都不能直接修改 state,唯一改变 state 的方法就是通过触发 action 来间接的修改。而这一看似繁琐的状态修改方式实际上反映了 Rudux 状态管理流程的核心思想,并因此保证了大型应用中状态的有效管理。 • 应用状态的改变通过纯函数来完成 Redux 使用纯函数方式来执行状态的修改,Action 表明了修改状态值的意图,而真正执行状态修改的则是 Reducer。且 Reducer 必须是一个纯函数,当 Reducer 接收到 Action 时,Action 并不能直接修改 State 的值,而是通过创建一个新的状态对象来返回修改的状态。redux 的三大元素和 Flux 框架不同,Redux 框架主要由 Action、Reducer 和 Store 三大元素组成。ActionAction 是一个普通的 JavaScript 对象,其中的 type 属性是必须的,用来表示 Action 的名称,type 一般被定义为普通的字符串常量。为了方便管理,一般通过 action creator 来创建 action,action creator 是一个返回 action 的函数。在 Redux 中,State 的变化会导致 View 的变化,而 State 状态的改变是通过接触 View 来触发具体的 Action 动作的,根据 View 触发产生的 Action 动作的不同,就会产生不同的 State 结果。可以定义一个函数来生成不同的 Action,这个函数就被称为 action creator。例如: const ADD_TODO = '添加事件 TODO'; function addTodo(text) { return { type: ADD_TODO, text } } const action = addTodo('Learn Redux');上面代码中,addTodo 就是一个 action creator。但当应用程序的规模越来越大时,建议使用单独的模块或文件来存放 action。Reducer当 Store 收到 action 以后,必须返回一个新的 State 才能触发 View 的变化,State 计算的过程即被称为 Reducer。Reducer 本质上是一个函数,它接受 Action 和当前 State 作为参数,并返回一个新的 State。格式如下: const reducer = function (state, action) { // ... return new_state; }; 为了保持 reducer 函数的纯净,请不要在 reducer 中执行如下的一些操作: • 修改传入参数; • 执行有副作用的操作,如 API 请求和路由跳转; • 调用非纯函数,如 Date.now() 或 Math.random()对于 Reducer 来说,整个应用的初始状态就可以直接作为 State 的默认值。例如: const defaultState = 0; const reducer = (state = defaultState, action) => { switch (action.type) { case 'ADD': return state + action.payload; default: return state; } }; //手动调用 const state = reducer(1, { type: 'ADD', payload: 2 }); 不过,在实际使用过程中,reducer 函数并不需要像上面那样进行手动调用,Store 的 store.dispatch 方法会触发 Reducer 的自动执行。为此,只需要在生成 Store 的时候将 Reducer 传入 createStore 方法即可。例如: import { createStore } from 'redux'; const store = createStore(reducer); 在上面的代码中,createStore 函数接受 Reducer 作为参数,该函数返回一个新的 Store,以后每当 store.dispatch 发送过来一个新的 Action,就会自动调用 Reducer 得到新的 State。StoreStore 就是数据保存的地方,可以把它看成一个容器,整个应用中只能有一个 Store。同时,Store 还具有将 Action 和 Reducers 联系在一起的作用。Store 具有以下的一些功能: • 维持应用的 state; • 提供 getState()方法获取 state; • 提供 dispatch(action)方法更新 state; • 通过 subscribe(listener)注册监听器; • 通过 subscribe(listener)返回的函数注销监听器。根据已有的 Reducer 来创建 Store 是一件非常容易的事情,例如 Redux 提供的 createStore 函数可以很方便的创建一个新的 Store。 import { createStore } from 'redux' import todoApp from './reducers' // 使用createStore函数创建Store let store = createStore(todoApp) 其中,createStore 函数的第二个参数是可选的,该参数用于设置 state 的初始状态。而这对于开发同构应用时非常有用的,可以让服务器端 redux 应用的 state 与客户端的 state 保持一致,并用于本地数据初始化。let store = createStore(todoApp, window.STATE_FROM_SERVER) Store 对象包含所有数据,如果想得到某个时刻的数据,则需要利用 state 来获取。例如: import { createStore } from 'redux'; const store = createStore(fn); //利用store.getState()获取state const state = store.getState(); Redux 规定,一个 state 只能对应一个 view,只要 state 相同得到的 view 就相同,这也是 Redux 框架的重要特性之一。到此,关于 Redux 的运作流程就非常的清晰了,下面总结下 Redux 的运作流程。当用户触摸界面时,调用 store.dispatch(action)捕捉具体的 action 动作。然后 Redux 的 store 自动调用 reducer 函数,store 传递两个参数给 reducer 函数:当前 state 和收到的 action。其中,reducer 函数必须是一个纯函数,该函数会返回一个新的 state。根 reducer 会把多个子 reducer 的返回结果合并成最终的应用状态,在这一过程中,可以使用 Redux 提供的 combineReducers 方法。使用 combineReducers 方法时,action 会传递给每个子的 reducer 进行处理,在子 reducer 处理后会将结果返回给根 reducer 合并成最终的应用状态。store 调用 store.subscribe(listener)监听 state 的变化,state 一旦发生改变就会触发 store 的更新,最终 view 会根据 store 数据的更新刷新界面。Redux 实现1,创建 storestore 就是 redux 的一个数据中心,简单的理解就是我们所有的数据都会存放在里面,然后在界面上使用时,从中取出对应的数据。因此首先我们要创建一个这样的 store,可以通过 redux 提供的 createStore 方法来创建。 export default function createStore(reducer, preloadedState, enhancer) { ... return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }可以看到 createStore 有三个参数,返回一个对象,里面有我们常用的方法,下面一一来看一下。getStategetState 用于获取当前的状态,格式如下: function getState() { return currentState }Redux 内部通过 currentState 变量保存当前 store,变量初始值即我们调用时传进来的 preloadedState,getState()就是返回这个变量。subscribe代码本身也不难,就是通过 nextListeners 数组保存所有的回调函数,外部调用 subscribe 时,会将传入的 listener 插入到 nextListeners 数组中,并返回 unsubscribe 函数,通过此函数可以删除 nextListeners 中对应的回调。以下是该函数的具体实现:var currentListeners = []; var nextListeners = currentListeners; function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice(); //生成一个新的数组 } } function subscribe(listener) { if (typeof listener !== "function") { throw new Error("Expected listener to be a function."); } var isSubscribed = true; ensureCanMutateNextListeners(); nextListeners.push(listener); return function unsubscribe() { if (!isSubscribed) { return; } isSubscribed = false; ensureCanMutateNextListeners(); var index = nextListeners.indexOf(listener); nextListeners.splice(index, 1); }; }可以发现,上面的源码使用 currentListeners 和 nextListeners 两个数组来保存,主要原因是在 dispatch 函数中会遍历 nextListeners,这时候可能会客户可能会继续调用 subscribe 插入 listener,为了保证遍历时 nextListeners 不变化,需要一个临时的数组保存。dispatch当 view dispatch 一个 action 后,就会调用此 action 对应的 reducer,下面是它的源码: function dispatch(action) { ... try { isDispatching = true currentState = currentReducer(currentState, action) //调用reducer处理 } finally { isDispatching = false } var listeners = currentListeners = nextListeners for (var i = 0; i < listeners.length; i++) { var listener = listeners[i] listener() } ... }从上面的源码可以发现,dispatch 函数在调用了 currentReducer 以后,遍历 nextListeners 数组,回调所有通过 subscribe 注册的函数,这样在每次 store 数据更新,组件就能立即获取到最新的数据。replaceReducerreplaceReducer 是切换当前的 reducer,虽然代码只有几行,但是在用到时功能非常强大,它能够实现代码热更新的功能,即在代码中根据不同的情况,对同一 action 调用不同的 reducer,从而得到不同的数据。function replaceReducer(nextReducer) { if (typeof nextReducer !== "function") { throw new Error("Expected the nextReducer to be a function."); } currentReducer = nextReducer; dispatch({ type: ActionTypes.REPLACE }); }bindActionCreatorsbindActionCreators 方法的目的就是简化 action 的分发,我们在触发一个 action 时,最基本的调用是 dispatch(action(param))。这样需要在每个调用的地方都写 dispatch,非常麻烦。bindActionCreators 就是将 action 封装了一层,返回一个封装过的对象,此后我们要出发 action 时直接调用 action(param)就可以了。react-reduxredux 作为一个通用的状态管理库,它不只针对 react,还可以作用于其它的像 vue 等。因此 react 要想完美的应用 redux,还需要封装一层,react-redux 就是此作用。react-redux 库相对简单些,它提供了一个 react 组件 Provider 和一个方法 connect。下面是 react-redux 最简单的写法: import { Provider } from 'react-redux'; // 引入 react-redux ... render( <Provider store={store}> <Sample /> </Provider>, document.getElementById('app'), ); connect 方法复杂点,它返回一个函数,此函数的功能是创建一个 connect 组件包在 WrappedComponent 组件外面,connect 组件复制了 WrappedComponent 组件的所有属性,并通过 redux 的 subscribe 方法注册监听,当 store 数据变化后,connect 就会更新 state,然后通过 mapStateToProps 方法选取需要的 state,如果此部分 state 更新了,connect 的 render 方法就会返回新的组件。 export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) { ... return function wrapWithConnect(WrappedComponent) { ... } } 最后【转自掘金】
2018年06月20日
288 阅读
0 评论
0 点赞
2018-05-21
this、apply、call、bind 区别
这又是一个经典问题~/(ㄒ o ㄒ)/~~也是 ES5 中众多坑中的一个,在 ES6 中可能会极大避免 this 产生的错误,但是为了一些老代码的维护,最好还是了解一下 this 的指向和 call、apply、bind 三者的区别。"this 的指向在 ES5 中,其实 this 的指向,始终坚持一个原理:this 永远指向最后调用它的那个对象,来,跟着我朗读三遍:this 永远指向最后调用它的那个对象,this 永远指向最后调用它的那个对象,this 永远指向最后调用它的那个对象。记住这句话,this 你已经了解一半了。下面我们来看一个最简单的例子: 例 1:var name = "windowsName"; function a() { var name = "Cherry"; console.log(this.name); // windowsName console.log("inner:" + this); // inner: Window } a(); console.log("outer:" + this); // outer: Window这个相信大家都知道为什么 log 的是 windowsName,因为根据刚刚的那句话“this 永远指向最后调用它的那个对象”,我们看最后调用 a 的地方 a();,前面没有调用的对象那么就是全局对象 window,这就相当于是 window.a();注意,这里我们没有使用严格模式,如果使用严格模式的话,全局对象就是 undefined,那么就会报错 Uncaught TypeError: Cannot read property 'name' of undefined。再看下这个例子: 例 2:var name = "windowsName"; var a = { name: "Cherry", fn: function() { console.log(this.name); // Cherry } }; a.fn();在这个例子中,函数 fn 是对象 a 调用的,所以打印的值就是 a 中的 name 的值。是不是有一点清晰了呢~我们做一个小小的改动: 例 3:var name = "windowsName"; var a = { name: "Cherry", fn: function() { console.log(this.name); // Cherry } }; window.a.fn();这里打印 Cherry 的原因也是因为刚刚那句话“this 永远指向最后调用它的那个对象”,最后调用它的对象仍然是对象 a。我们再来看一下这个例子: 例 4:var name = "windowsName"; var a = { // name: "Cherry", fn: function() { console.log(this.name); // undefined } }; window.a.fn();这里为什么会打印 undefined 呢?这是因为正如刚刚所描述的那样,调用 fn 的是 a 对象,也就是说 fn 的内部的 this 是对象 a,而对象 a 中并没有对 name 进行定义,所以 log 的 this.name 的值是 undefined。这个例子还是说明了:this 永远指向最后调用它的那个对象,因为最后调用 fn 的对象是 a,所以就算 a 中没有 name 这个属性,也不会继续向上一个对象寻找 this.name,而是直接输出 undefined。再来看一个比较坑的例子: 例 5:var name = "windowsName"; var a = { name: null, // name: "Cherry", fn: function() { console.log(this.name); // windowsName } }; var f = a.fn; f();这里你可能会有疑问,为什么不是 Cherry,这是因为虽然将 a 对象的 fn 方法赋值给变量 f 了,但是没有调用,再接着跟我念这一句话:“this 永远指向最后调用它的那个对象”,由于刚刚的 f 并没有调用,所以 fn() 最后仍然是被 window 调用的。所以 this 指向的也就是 window。由以上五个例子我们可以看出,this 的指向并不是在创建的时候就可以确定的,在 es5 中,永远是this 永远指向最后调用它的那个对象。再来看一个例子: 例 6:var name = "windowsName"; function fn() { var name = "Cherry"; innerFunction(); function innerFunction() { console.log(this.name); // windowsName } } fn();读到现在了应该能够理解这是为什么了吧(o ゚ ▽ ゚)o。怎么改变 this 的指向改变 this 的指向我总结有以下几种方法:使用 ES6 的箭头函数在函数内部使用 _this = this使用 apply、call、bindnew 实例化一个对象例 7:var name = "windowsName"; var a = { name: "Cherry", func1: function() { console.log(this.name); }, func2: function() { setTimeout(function() { this.func1(); }, 100); } }; a.func2(); // this.func1 is not a function在不使用箭头函数的情况下,是会报错的,因为最后调用 setTimeout 的对象是 window,但是在 window 中并没有 func1 函数。我们在改变 this 指向这一节将把这个例子作为 demo 进行改造。箭头函数众所周知,ES6 的箭头函数是可以避免 ES5 中使用 this 的坑的。箭头函数的 this 始终指向函数定义时的 this,而非执行时。,箭头函数需要记着这句话:“箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined”。例 8 :var name = "windowsName"; var a = { name: "Cherry", func1: function() { console.log(this.name); }, func2: function() { setTimeout(() => { this.func1(); }, 100); } }; a.func2(); // Cherry在函数内部使用 _this = this如果不使用 ES6,那么这种方式应该是最简单的不会出错的方式了,我们是先将调用这个函数的对象保存在变量 _this 中,然后在函数中都使用这个 _this,这样 _this 就不会改变了。 例 9:var name = "windowsName"; var a = { name: "Cherry", func1: function() { console.log(this.name); }, func2: function() { var _this = this; setTimeout(function() { _this.func1(); }, 100); } }; a.func2(); // Cherry这个例子中,在 func2 中,首先设置 var _this = this;,这里的 this 是调用 func2 的对象 a,为了防止在 func2 中的 setTimeout 被 window 调用而导致的在 setTimeout 中的 this 为 window。我们将 this(指向变量 a) 赋值给一个变量 _this,这样,在 func2 中我们使用 _this 就是指向对象 a 了。使用 apply、call、bind使用 apply、call、bind 函数也是可以改变 this 的指向的,原理稍后再讲,我们先来看一下是怎么实现的:使用 apply例 10:var a = { name: "Cherry", func1: function() { console.log(this.name); }, func2: function() { setTimeout( function() { this.func1(); }.apply(a), 100 ); } }; a.func2(); // Cherry使用 call例 11:var a = { name: "Cherry", func1: function() { console.log(this.name); }, func2: function() { setTimeout( function() { this.func1(); }.call(a), 100 ); } }; a.func2(); // Cherry使用 bind例 12:var a = { name: "Cherry", func1: function() { console.log(this.name); }, func2: function() { setTimeout( function() { this.func1(); }.bind(a)(), 100 ); } }; a.func2(); // Cherryapply、call、bind 区别刚刚我们已经介绍了 apply、call、bind 都是可以改变 this 的指向的,但是这三个函数稍有不同。在 MDN 中定义 apply 如下;apply() 方法调用一个函数, 其具有一个指定的 this 值,以及作为一个数组(或类似数组的对象)提供的参数语法:fun.apply(thisArg, [argsArray])thisArg:在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动指向全局对象(浏览器中就是 window 对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。argsArray:一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从 ECMAScript 5 开始可以使用类数组对象。浏览器兼容性请参阅本文底部内容。apply 和 call 的区别其实 apply 和 call 基本类似,他们的区别只是传入的参数不同。call 的语法为: fun.call(thisArg[, arg1[, arg2[, ...]]]) 所以 apply 和 call 的区别是 call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。例 13:var a = { name: "Cherry", fn: function(a, b) { console.log(a + b); } }; var b = a.fn; b.apply(a, [1, 2]); // 3例 14:var a = { name: "Cherry", fn: function(a, b) { console.log(a + b); } }; var b = a.fn; b.call(a, 1, 2); // 3bind 和 apply、call 区别我们先来将刚刚的例子使用 bind 试一下var a = { name: "Cherry", fn: function(a, b) { console.log(a + b); } }; var b = a.fn; b.bind(a, 1, 2);我们会发现并没有输出,这是为什么呢,我们来看一下 MDN 上的文档说明:bind()方法创建一个新的函数, 当被调用时,将其 this 关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。所以我们可以看出,bind 是创建一个新的函数,我们必须要手动去调用:var a = { name: "Cherry", fn: function(a, b) { console.log(a + b); } }; var b = a.fn; b.bind(a, 1, 2)(); // 3==================================== 更新==============================JS 中的函数调用看到留言说,很多童靴不理解为什么 例 6 的 innerFunction 和 例 7 的 this 是指向 window 的,所以我就来补充一下 JS 中的函数调用。 例 6:var name = "windowsName"; function fn() { var name = "Cherry"; innerFunction(); function innerFunction() { console.log(this.name); // windowsName } } fn();例 7:var name = "windowsName"; var a = { name: "Cherry", func1: function() { console.log(this.name); }, func2: function() { setTimeout(function() { this.func1(); }, 100); } }; a.func2(); // this.func1 is not a function函数调用的方法一共有 4 种作为一个函数调用函数作为方法调用使用构造函数调用函数作为函数方法调用函数(call、apply)作为一个函数调用比如上面的 例 1: 例 1:var name = "windowsName"; function a() { var name = "Cherry"; console.log(this.name); // windowsName console.log("inner:" + this); // inner: Window } a(); console.log("outer:" + this); // outer: Window这样一个最简单的函数,不属于任何一个对象,就是一个函数,这样的情况在 JavaScript 的在浏览器中的非严格模式默认是属于全局对象 window 的,在严格模式,就是 undefined。但这是一个全局的函数,很容易产生命名冲突,所以不建议这样使用。函数作为方法调用所以说更多的情况是将函数作为对象的方法使用。比如例 2: 例 2:var name = "windowsName"; var a = { name: "Cherry", fn: function() { console.log(this.name); // Cherry } }; a.fn();这里定义一个对象 a,对象 a 有一个属性(name)和一个方法(fn)。然后对象 a 通过 . 方法调用了其中的 fn 方法。然后我们一直记住的那句话“this 永远指向最后调用它的那个对象”,所以在 fn 中的 this 就是指向 a 的。使用构造函数调用函数如果函数调用前使用了 new 关键字, 则是调用了构造函数。 这看起来就像创建了新的函数,但实际上 JavaScript 函数是重新创建的对象:// 构造函数: function myFunction(arg1, arg2) { this.firstName = arg1; this.lastName = arg2; } // This creates a new object var a = new myFunction("Li", "Cherry"); a.lastName; // 返回 "Cherry"这就有要说另一个面试经典问题:new 的过程了,(ಥ_ಥ) 这里就简单的来看一下 new 的过程吧: 伪代码表示: var a = new myFunction("Li","Cherry"); new myFunction{ var obj = {}; obj.__proto__ = myFunction.prototype; var result = myFunction.call(obj,"Li","Cherry"); return typeof result === 'obj'? result : obj; }创建一个空对象 obj;将新创建的空对象的隐式原型指向其构造函数的显示原型。使用 call 改变 this 的指向如果无返回值或者返回一个非对象值,则将 obj 返回作为新对象;如果返回值是一个新对象的话那么直接直接返回该对象。所以我们可以看到,在 new 的过程中,我们是使用 call 改变了 this 的指向。作为函数方法调用函数在 JavaScript 中, 函数是对象。JavaScript 函数有它的属性和方法。 call() 和 apply() 是预定义的函数方法。 两个方法可用于调用函数,两个方法的第一个参数必须是对象本身在 JavaScript 严格模式(strict mode)下, 在调用函数时第一个参数会成为 this 的值, 即使该参数不是一个对象。 在 JavaScript 非严格模式(non-strict mode)下, 如果第一个参数的值是 null 或 undefined, 它将使用全局对象替代。这个时候我们再来看例 6: 例 6:var name = "windowsName"; function fn() { var name = "Cherry"; innerFunction(); function innerFunction() { console.log(this.name); // windowsName } } fn();这里的 innerFunction() 的调用是不是属于第一种调用方式:作为一个函数调用(它就是作为一个函数调用的,没有挂载在任何对象上,所以对于没有挂载在任何对象上的函数,在非严格模式下 this 就是指向 window 的)然后再看一下 例 7: 例 7:var name = "windowsName"; var a = { name: "Cherry", func1: function() { console.log(this.name); }, func2: function() { setTimeout(function() { this.func1(); }, 100); } }; a.func2(); // this.func1 is not a function这个简单一点的理解可以理解为“匿名函数的 this 永远指向 window”,你可以这样想,还是那句话this 永远指向最后调用它的那个对象,那么我们就来找最后调用匿名函数的对象,这就很尴尬了,因为匿名函数名字啊,笑哭,所以我们是没有办法被其他对象调用匿名函数的。所以说 匿名函数的 this 永远指向 window。如果这个时候你要问,那匿名函数都是怎么定义的,首先,我们通常写的匿名函数都是自执行的,就是在匿名函数后面加 () 让其自执行。其次就是虽然匿名函数不能被其他对象调用,但是可以被其他函数调用啊,比如例 7 中的 setTimeout。本文转自:cherryblog.site/
2018年05月21日
287 阅读
0 评论
0 点赞
1
...
4
5