React16.8.0+版本才支持Hooks;彻底可选,能够只在部分函数组件里面应用Hooks,不必彻底应用Hooks到整个项目;彻底向后兼容,没有重大改变;尚无计 划移除Class组件,下文会有过渡策略;Hooks没有新概念,也没有修改旧理念,只是一种更加简单直接的API,来对既有的props,state,context,refs, lifeCycle作强有力的组合;Hooks能够解决许多表面看似不相关的问题。html
React没有提供一个将可复用行为(比方说connect到store)粘贴到组件的方式。render props和higher-order components能够解决这类问题。可是这些 模式须要对相关组件进行重构。有可能会略显笨重,较复杂的话很难继续改造。在React DevTools里面会发现组件的‘wrapper hell‘,有providers、 consumers、higher-order components、render props以及其余抽象层。node
有了Hooks的话,咱们能够从一个组件当中抽取出stateFul logic,单独测试并复用起来。这些都是Hooks定制化的基石。react
咱们发现一些组件一开始很简单,可是慢慢变得不易于维护,处处弥漫着stateFul logic、反作用。每一个生命周期钩子方法常常混合着许多不相关逻辑。例如, 组件在componentDidMount和componentDidUpdate中请求数据,可是componentDidMount方法也有可能包含注册事件监听,在componentWillUnmount中 取消事件监听。一同变化的相关代码被拆分开来了,彻底不相关的代码却在一个方法里结合起来了。这样很容易引入bug。git
就像Svelte、Angular、Glimmer那样,组件预编译会在将来释放巨大潜力。特别是不局限于模板,最近React团队在使用Prepack探索component folding:github
function Foo (props) {
if (props.data.type === 'img') {
return <img src={props.data.src} className={props.className} alt={props.alt} />
}
return <span>{props.data.type}</span>
}
Foo.defaultProps = {
alt: 'An image of Foo'
}
var CSSClasses = {
bar: 'bar'
}
module.exports = CSSClasses;
var Foo = require('Foo')
var Classes = require('Classes')
function Bar (props) {
return <Foo data={{ type: 'img', src: props.src }} className={Classes.bar} />
}
function Bar_optimized (props) {
return <img src={props.src} className="bar" alt="An image of Foo." />
}
// All above is called `Dead-code Elimination`
复制代码
发现class组件会有潜在模式,使得component folding作出的优化回退到一个更慢的结果。classes也存在诸如这些问题:压缩不足,热加载分离成片状,不可靠。 咱们想要推出一款API使得代码依然保持住component folding作出的优化。 是他是他就是他了,Hooks让你可以不用Classes却能使用上已有的React特性。React 组件老是贴近于函数。Hooks拥抱函数,可是不会牺牲React的精髓,而且 不要求你掌握复杂的函数式、响应式编程技巧。web
Hooks和现有代码是并行关系,不提倡把现有class组件改写为hooks,特别是复杂的大组件,可是新组件能够采用Hooks来写^_^npm
import React, { useState } from 'react'
function Example () {
const [count, setCount] = useState(0)
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div>
)
}
// equivalent to
class Example extend React.Component {
constructor (props) {
super(props)
this.state = {
count: 0
}
}
render () {
return (
<div> <p>You clicked {count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}>Click me</button> </div>
)
}
}
复制代码
这时候咱们再来解释一下React Hooks:一个Hook是一个可以hook into
React特性的特殊函数。useState
是一个增长React state到函数组件的 Hook。编程
执行组件里的反作用,和class组件的生命周期方法类似。useEffect
Hook是 componentDidMount, componentDidUpdate, componentWillUnmount
的结合体json
import React, { useState, useEffect } from 'react'
function Example () {
const [count, setCount] = useState(0)
// Similar to componentDidMount and componentDidUpdate
useEffect(() => { // this arrow method is called the effect method, you can also name it rather than arrow function
// using arrow function, if there's lot code, we should add remark, on the other side, we can use multiple useEffect
// with named-effect-function if you like
// Update the document title using the browser API
document.title = `You clicked ${count} times`
})
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div>
)
}
// equivalent to
class Example extend React.Component {
constructor (props) {
super(props)
this.state = {
count: 0
}
}
componentDidMount () {
document.title = `You clicked ${this.state.count} times`
}
// duplicate code in another lifeCycle method
componentDidUpdate () {
document.title = `You clicked ${this.state.count} times`
}
render () {
return (
<div> <p>You clicked {count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}>Click me</button> </div>
)
}
}
复制代码
React组件有两种反作用:一种不要求cleanup,另外一种要求cleanup。redux
有时候,咱们想要在React更新DOM以后运行一些代码。网络请求、DOM手动操做、日志、以及其余的命令式API是常见的不要求cleanup的effects。咱们说咱们运行完后就马上忘记这块 代码。 在class组件里,render
方法不会带来反作用,咱们会在componentDidMount, componentDidUpdate
当中放入反作用。能够看到上面的代码在两个生命 周期方法里重复了,由于咱们想要的效果是无论组件是挂载完仍是更新完都去执行一样的反作用。也就是说咱们想要每次渲染后都执行一样的某个逻辑,可是 React没有这样的方法,咱们能够抽成一个独立方法可是仍然须要在两个生命周期方法里分别调用一次 useEffect能够读取函数组件内的state、props这一能力依赖于函数做用域。Hooks拥抱JavaScript闭包因此避免引入React特定API,由于闭包已经作到了, React Hooks原理能够参考github源码,之后我会抽时间整理成一篇博客。 若是你简单的理解每一个useEffect都是在每次render以后执行的话,那你可能还不知道useEffect的第二个参数(影响因子数组)。 有经验的开发者可能注意到useEffect的第一个方法参数每次渲染后都会不同。没错,这样咱们就能够在effect
内部读取到最新的count
数据,每次渲染后 ,都会有一个不一样的新effect方法替换以前的旧effect方法。某种程度上,这使得effects表现的更像render结果的一部分,每一个effect属于一个render。
和componentDidMount, componentDidUpdate
不一样的是,effects不会阻止浏览器更新渲染帧。webApp响应性会更好。大部分effects没必要同步发生。 在一些不常见的场景好比计算布局的时候,须要同步的effects。能够采用区别于useEffect
的useLayoutEffect
API。
像事件的添加与销毁、subscribe && unsubscribe等等。
不要在循环、条件或者嵌套语句中调用Hooks。
咱们要在React函数组件内部最外层直接调用Hooks,这样,Hooks能够在每一次组件渲染的时候保持顺序一致,React也能够正确保存屡次调用useState useEffect
之间的Hooks状态。能够在React函数组件、custom Hooks内部调用Hooks,可是不要在正常的JavaScript函数内部调用Hooks。这样能够确保组件内部的 全部stateFul logic
可读性强。若是写JavaScript而不是TypeScript的话能够install eslint-plugin-react-hooks
:
// Your eslint configuration
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error", // Checks rules of hooks
"react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
}
}
复制代码
如今咱们来解释下为何须要把Hooks调用放在函数组件内部最外层:
function Form () {
// 1. Use the name state variable
const [ name, setName ] = useState('Mary')
useEffect(function persistForm () {
localStorage.setItem('formData', name)
})
const [ surname, setSurname ] = useState('Poppins')
useEffect(function updateTitle () {
document.title = `${name} ${surname}`
})
}
// React怎么就知道哪一个state是哪一个useState调用出来的呢,其实依赖于Hooks的调用顺序。咱们的例子能够起做用的缘由在于每次渲染时Hooks的调用顺序
// 保持一致!
// First Render
useState('Mary') // 1. Initialize the name state variable with 'Mary'
useEffect(persistForm) // 2. Add an effect for persisting the form
useState('Poppins') // 3. Initialize the surname state
// Second Render
useState('Mary') // 1. Read the name state variable (argument is ignored)
useEffect(persistForm) // 2. Replace the effect for persisting the form
useState('Poppins') // 3. Read the surname state variable(argument is ignored)
useEffect(updateTitle) // 4. Replace the effect for updating the title
复制代码
只要每次渲染的Hooks调用顺序保持一致,React就能够把一些本地state数据和每次调用关联起来。可是把一个Hook调用放入一个condition的话:
if (name !== '') {
useEffect(function persistForm () {
localStorage.setItem('formData', name)
})
}
复制代码
第一次渲染时name !== ''
条件是true
,执行了这条Hook,可是,下次渲染时用户有可能清除了表单,条件值为false
,跳过了这条Hook,Hooks调用顺序 改变了:
useState('Mary') // 1. Read the name state variable (argument is ignored)
// useEffect(persistForm) // This Hook was skipped!
useState('Poppins') // 2(but was 3). Failed to read the surname state variable
useEffect(updateTitle) // 3(but was 4). Failed to replace
复制代码
React在第二次useState
调用时并不知道要return什么。React期待组件内第二个Hook调用是persistForm
effect,就像上一次render时那样,然而并 不是。咱们跳过的Hook后面的每一个Hook都会迁移一位,引来bug。 若是咱们想条件性地执行一个effect,咱们要把条件放到Hook内部:
useEffect(function persistForm () {
if (name !== '') {
localStorage.setItem('formData', name)
}
})
复制代码
构建本身的Hooks,使得分离组件逻辑到可复用地函数组件去。 以前咱们学习Effect Hook时,看到用一条信息代表朋友是否在线的聊天应用:
import React, { useState, useEffect } from 'react'
function FriendStatus (props) {
const [ isOnline, setIsOnline ] = useState(null)
useEffect(() => {
function handleStatusChange (status) {
setIsOnline(status.isOnline)
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange)
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange)
}
})
if (isOnline === null) return 'Loading...'
return `O${isOnline ? 'n' : 'ff'}line`
}
复制代码
如今应用要增长联系人列表,并且须要展现绿色在线状态的许多姓名,咱们能够吧类似逻辑复制到咱们的FriendListItem
组件可是不够理想:
import React, { useState, useEffect } from 'react'
function FriendListItem(props) {
const [isOnline, setIsOnline] = useState(null)
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline)
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange)
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange)
}
})
return (
<li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li>
)
}
复制代码
换句话说,如何复用FriendStatus FriendListItem
的逻辑呢。之前针对class组件,咱们有render props, higher-order components
两个手段来复用组件内部 逻辑。对于Hooks的话,又该如何在不增长组件树层级的状况下解决一样的问题呢。方案就是:
在JavaScript函数间复用逻辑的办法是剥离出另外一个函数,函数组件和Hooks都是函数,因此同理喽! 一个自定义Hook是一个以“use“打头的JavaScript函数,它调用了其余Hooks:
import React, { useState, useEffect } from 'react'
function useFriendStatus (friendID) {
const [ isOnline, setIsOnline ] = useState(null)
useEffect(() => {
function handleStatusChange (status) {
setIsOnline(status.isOnline)
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange)
return () => {
ChatAPI.unsubscribeToFriendStatus(friendID, handleStatusChange)
}
})
return isOnline
}
复制代码
和React 组件不同的是,一个自定义Hook不用特定签名。咱们自行决定参数,返回值,和正常函数同样,惟一不一样的就是以use
开头,这样就能方便应用rules of Hooks
。 如今看看如何使用自定义Hook:
function FriendStatus (props) {
const isOnline = useFiendStatus(props.friend.id)
if (isOnline === null) return 'Loading'
return `O${isOnline ? 'n' : 'ff'}line`
}
function FriendListItem (props) {
const isOnline = useFiendStatus(props.friend.id)
return (
<li style={{ color: isOnline ? 'green' : 'black' }}> {prop.friend.name} </li>
)
}
复制代码
咱们约定了自定义Hooks必须yiuse
开头,很是重要,不然咱们没法检测Hooks规则,由于没法判断一个函数组件内部是否包含Hooks。 使用相同Hook的两个函数组件共享状态吗?No,自定义Hooks时复用组件本地逻辑的机制,好比发起订阅、记住当前值,可是每次使用一个自定义Hook的时候,它的所 有state和effects都是独立的。 一个自定义的Hook如何获取独立的state?一个Hook的每次调用都有独立的状态。由于咱们是直接调用了useFriendStatus
,React认为咱们的组件仅仅调用了 useState useEffect
。
自定义Hooks提供了React组件史无前例的复用逻辑地灵活性。咱们能够处理表单、动画制做、声明式订阅、定时器、等等。 不要过早增长抽象。既然函数组件能够作不少,那么一不当心就会增长函数长度,这很正常,可是不用以为马上写成Hooks。
若是更新逻辑复杂或者数据须要聚合的话(比方说表单),能够考虑用useReducer
代替useState
,也用到了redux的思想,不亦乐乎。
const [ state, setState ] = useState(initialState)
复制代码
首次渲染时,返回的state就是initialState
。setState
是用来更新state的方法,参数是新的state值,enqueue进组件从新渲染的队列。
若是新state时前一个state计算出来的,能够给 setState
传递一个函数,该函数接收前一个state做为参数,而后return新的state:
function Counter (initialCount) {
const [ count, setCount ] = useState(initialCount)
return (
<> Count: {count} <button onClick={() => setCount(initialCount)}>Reset</button> <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button> <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button> </> ) } 复制代码
和class组件 setState
不一样的是,useState
不会自动合并更新对象。可是咱们能够采用ES6扩展符+函数更新形式来作:
setState(prevState => ({ ...prevState, ...updatedValues }))
复制代码
另外就是能够采用useReducer
,更适合包含多个子值的state对象。
若是初始值initialState
是复杂计算的结果呢:
const [ state, setState ] = useState(() => {
const initialState = someExpensiveComputation(props)
return initialState
})
复制代码
React采用Object.is
来比较先后state,以此来决定是否更新children或者触发effects。
React有可能在bail out(先后state一致,不会从新渲染)以前仍然再次渲染特定组件。咱们没必要在意那么多,由于React不会进入到更深层级。 固然若是你在渲染时须要作复杂的计算,能够用useMemo
来优化。
useEffect(didUpdate)
didUpdate
是包含声明式、或者反作用的方法,好比订阅、订阅、定时器、日志、等等反作用代码容许直接置于React函数组件内部(render phase
),要 写反作用代码的话就用useEffect
吧。
清除反作用
反作用常常搞出一些须要在组件卸载以前清除的资源,好比说订阅、定时器ID。这时候咱们可让useEffect
return一个清除器函数,来避免内存泄露:
useEffect(() => {
const subscription = props.source.subscribe()
return () => {
subscription.unsubscribe()
}
})
复制代码
另外,前一个effect会在执行下一个effect以前被清除掉。咱们的例子中,每次更新时都会有一次新订阅。那么怎么避免每次更新时触发一次effect呢。
不像componentDidMount componentDidUpdate
,传递给useEffect
的方法会在layout、paint后触发,在一次延迟事件中。这对于大多数像订阅、事件 处理器这样的反作用都比较合适,由于大多数都不会阻塞浏览器更新帧。
可是,不是全部的反作用都会被推迟。好比对于用户可见的DOM修改必须在下一次重绘以前同步触发,这样用户就不会感受到卡顿。对于这类effects,React提供 一个叫作useLayoutEffect
的Hook。和useEffect
一个样子,只是触发时机不一致。
尽管useEffect
被推迟到浏览器重绘以后,却能在每次渲染以前触发。React总会在一次更新以前把上一次渲染的effects清除掉。
有条件地触发一次effect
拿上面的订阅器例子来讲,每次更新时,咱们其实能够只在source改变时去从新订阅:
// 给useEffect传递第二个数组参数,只有当数组里面的变量变化时才去触发该effect
useEffect(() => {
const subscription = props.source.subscribe()
return () => {
subscription.unsubscribe()
}
}, [props.source])
复制代码
须要注意的是,要确认数组元素是完备的,全部能决定该effect发生与否的变量都需列出。要否则有可能会错误地引用旧渲染中的值。
若是你想执行一次effect,而且也只清除一次(mount unmount
),那么能够传递空数组[]
做为第二个参数。React知道此effect不依赖任何props、state 数据,因此不会从新执行。
咱们推荐使用eslint-plugin-react-hooks的时候使用exhaustive-deps
规则,依赖没有正确指定时会有警告,并告知如何修复。
每个effect方法内部引用的值都应该在依赖列表中出现。不久的未来,一个高级编译器可以自动构建该数组。
useContext
const value = useContext(MyContext) // Accepts a context object(the value returned from React.createContext)
复制代码
一个调用useContext
的组件总会在context变更时从新渲染。若是从新渲染组件很耗性能,那么请使用useMemo
若是你以前熟悉context API的话,应该懂得useContext(MyContext)
等同于类组件的static contextType = MyContext
或者<MyContext.Consumer>
useContext(MyContext)
只会让你读取context,而且订阅它的变更。你仍然在组件树中须要<MyContext.Provider>
,来为此context提供值
下面的Hooks要么是基础hooks的变体,要么是特定场合下的的需求。不用担忧提早学习没用的东西。
useReducer
const [state, dispatch] = useReducer(reducer, initialState)
// or
const [state, dispatch] = useReducer(reducer, initialArg, init) // 初始state会被设置成 init(initialArg)
// 你能够把计算初始state的逻辑抽离到reducer以外,这样也有利于初始化state的action操做:
function init (initialCount) {
return { count: initialCount }
}
function reducer (state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 }
case 'decrement':
return { count: state.count - 1 }
case 'reset':
return init(action.payload)
default:
throw new Error()
}
}
function Counter ({ initialCount }) {
const [ state, dispatch ] = useReducer(reducer, initialCount, init)
return (
<> Count: {state.count} <button onClick={() => dispatch({ type: 'reset', payload: initialCount })}> Reset </button> <button onClick={() => dispatch({ type: 'increment' })}>+</button> <button onClick={() => dispatch({ type: 'decrement' })}>-</button> </> ) } 复制代码
useCallback
const memoizedCb = useCallback(
() => {
doSomething(a, b)
},
[ a, b ],
)
复制代码
做用相似于shouldComponentUpdate
useCallback(fn, deps)
等同于useMemo(() => fn, deps)
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [ a, b ])
复制代码
useRef
const refContainer = useRef(initialVal)
复制代码
useRef
返回一个可改变的ref对象,.current
属性初始化成initialValue
参数。返回的对象会在组件的生命周期内始终存留。 好比说要命令式地访问子组件:
function TextInputWithFocusButton () {
const inputEl = useRef(null)
const onBtnClick = () => {
// `current` 指向挂载了的输入框元素
inputEl.current.focus()
}
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onBtnClick}>Focus the input</button>
</>
)
}
复制代码
可是useRef()
比ref
属性更有用。就像class实例属性同样方便。
useRef
不会在内容变更时通知你,改变.current
属性不会引起从新渲染。若是你想要在React挂载或者卸载某个DOM节点的ref时执行某些代码的话,采用 callback ref吧.
useImperativeHandle
useImperativeHandle(
ref,
() => {
handler
}, // createHandle
[input], // deps
)
复制代码
useImperativeHandle
自定义了使用ref
时暴露给父组件的实例值。正常状况下,使用refs的命令式代码应该避免。userImperativeHandle
应该和 forwardRef
一块儿使用。
function FancyInput (props, ref) {
const inputRef = useRef()
useImperatieHandle(ref, () => ({
focus: () => {
inputRef.current.focus()
},
}))
return <input ref={inputRef} ... /> } FancyInput = forwardRef(FancyInput) 复制代码
一个渲染<FancyInput ref={FancyInputRef} />
的父组件,可以调用fancyInputRef.current.focus()
useLayoutEffect 形式相似useEffect
,可是在全部DOM操做以后马上触发。用它去从DOM中读取layout而后从新渲染。
useLayoutEffect
和componentDidMount componentDidUpdate
触发阶段一致。咱们推荐尽可能多用useEffect
,只有遇到问题才去尝试useLayoutEffect
。
服务端渲染的话,useLayoutEffect useEffect
都是在JavaScript下载后才去执行,因此React会对包含useLayoutEffect
的服务端渲染组件发出⚠️。 有两个修复手段:把逻辑搬进useEffect
(若是第一次渲染没有必要);推迟组件显示到客户端渲染(若是直到useLayoutEffect
执行后HTML才显得完整)。
为了从服务端渲染HTML中剔除须要layout effects的组件,采用条件渲染:showChild && <Child />
而且推迟显示:useEffect(() => { setShowChild(true) }, [])
。这样的话UI就不会有broken现象。
useDebugValue
useDebugValue(value)
复制代码
用于在React开发者工具中显示自定义hooks的标签:
function useFriendStatus (friendID) {
const [ isOnline, setIsOnline ] = useState(null)
// ...
// Show a label in DevTools next to this Hook
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline')
return isOnline
}
复制代码
这里有一篇Dan Abramov在medium上面发表的文章。
目标是尽快吧,可是getSnapshotBeforeUpdate componentDidCatch
这两个钩子暂时尚未覆盖到,可是处于计划当中。
常常吧,render props和高阶组件都只渲染一个子组件。咱们把Hooks看做一个简洁之法。可是render props、高阶组件仍是有额外用武之地: 一个有renderItem
属性的虚拟滚动条组件、包含特定DOM结构的虚拟容器组件。可是大多数状况下,Hooks足够有用,能够减小组件树深度。
不会。 不过在未来,这些库的新版本有可能会有一些自定义Hooks:useRedux() useRouter()
这样的,就用不上容器组件了。
基于函数,你说呢。比起HOC,TypeScript彻底拥抱React Hooks。
constructor
: 函数组件不须要构造器,能够用useState初始化state。getDerivedStateFromProps
: 能够尝试在render时更新shouldComponentUpdate
: 用React.memo
来实现render
: 函数体自己componentDidMount, componentDidUpdate, componentWillUnmount
: useEffect
均可以作到componentDidCatch, getDerivedStateFromError
: 暂不支持,之后会增长相应Hook这里有个codesandbox的Demo, 还有这篇文章讲得很全。
useRef()
不只仅是DOM引用。ref
对象是一个带有current
属性的通用容器,他能够被修改,容纳任何值,就有一个类的实例属性:
function Timer () {
const intervalRef = useRef()
useEffect(() => {
const id = setInterval(() => {
// ...
})
intervalRef.current = id
return () => {
clearInterval(intervalRef.current)
}
})
}
复制代码
function Counter () {
const [ count, setCount ] = useState(0)
const prevCountRef = useRef()
useEffect(() => {
prevCountRef.current = count
})
const prevCount = prevCountRef.current
return <h1>Now: {count}, before: {prevCount}</h1>
}
复制代码
为了测量DOM节点的尺寸、大小。用callback ref吧:
function MeasureExample () {
const [ height, setHeight ] = useState(0)
const measuredRef = useCallback(node => {
if (node !== null) setHeight(node.getBoundingClientRect().height)
}, [])
return (
<> <h1 ref={measuredRef}>Hello, world</h1> <h2>The above header is {Math.round(height)}px tall</h2> </> ) } 复制代码
上面例子中咱们为何不用useRef
呢,由于一个ref对象不会在ref值变更的时候发出通知。采用回调ref能够作到子组件也能够显示测量好的node节点。
另外还有许许多多的Hooks FAQ,你们能够多看看React官网FAQ。
Hooks 相关的博客、资源我找了一些挺不错的:
star数上千的github项目却并很少,博客比较少; 我能找到比较好的资源有(大侠看到好的资源能够留个言?):
总结一下,咱们应该花更多的精力多看官网对其原理机制、使用的阐述,还有多研究一下高级hooks、自定义hooks;能够贡献到github或者写成博客,这样hooks社区就会好不少, 另外有空能够看看源码。