react design patterns 声明式 声明式 vs 命令式 1 2 3 4 5 6 quicksort :: (Ord a) => [a] -> [a]quicksort [] = []quicksort (x:xs) = let smallerSorted = quicksort [ a | a <- xs , a <= x] biggerSorted = quicksort [a | a <- xs , a > x] in smallerSorted ++ [x] ++ biggerSorted
对于快排,haskell 的 quicksort 每轮递归的描述是:smallerSorted 是由 xs 中小于 x 的数组合而成的数组快排的结果。 biggerSorted 是由 xs 中大于 x 的数组合而成的数组快排的结果 该轮递归的返回值是 smallerSorted ++ [x] ++ biggerSorted.(三个数组拼接)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void quickSort (int s[], int l, int r) { if (l< r) { int i = l, j = r, x = s[l]; while (i < j) { while (i < j && s[j]>= x) j--; if (i < j) s[i++] = s[j]; while (i < j && s[i]< x) i++; if (i < j) s[j--] = s[i]; } s[i] = x; quickSort(s, l, i - 1 ); quickSort(s, i + 1 , r); } }
对于cpp 的 quicksort 每轮递归的描述是:(以i作为左指针,以j作为右指针),从r至l找到第一个大于x的数s[j],交换s[i]与s[j]的值,然后从i++至j找到第一个小于x的数s[i],交换s[i]与s[j],然后从j–向i找第一个大于x的数s[j]……(不断重复这个过程直至i、j两指针相遇,将 x 赋值给 s[i]。(一个混乱的非完整描述……)
同样一个分治算法实现的快排,不同的范式语言可以感受到很大的差别,除了代码量的差别外,haskell的快排函数可以用是什么 这样的句子解释,cpp的快排函数用的是步骤 解释。也就是我们常说的 what 和 how 的差别。
虽然从这个快排的例子上看,declarative 比 imperative 用了更少的代码量,和更简洁的处理逻辑,但是也不意味着声明式能在众多应用情景中优于命令式,有的时候下定义比描述过程更为困难。
react 声明式的体现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function addArtistNameToBody ( ) { const bodyTag = document .querySelector('body' ) const divTag = document .createElement('div' ) let h1Tag = document .createElement('h1' ) h1Tag.innerText = "Mitski" divTag.append(h1Tag) bodyTag.append(divTag) } class Artist extends Component { render() { return ( <div> <h1>{this .props.name}</h1> </ div>) } }
前者体现的dom节点的构建过程,后者体现的组件含有的内容。
函数式 个人不完全正确的理解:尽可能的对逻辑块独立的地方进行函数抽取,无论当下复用情况高不高,只要它这部分的代码逻辑相对独立。这样做的好处是具有更明显的抽象层次,使得代码更易读,和代码改动更易进行。纯函数式相对一般的函数式,是不会产生副作用,不会改变函数外部的任何的状态(变量的值)。据我所知(没有经过验证),纯函数式是由于不存在变量,或者说存在一种变量声明即需要初始化,且之后不可更改它的值,所以函数调用是无法产生副作用的,无副作用的强大之处在于代码更可控 ,意味这你不必去跟踪一个变量在何处被意外的改变等等(不过我也没对纯函数式语言的项目debug过)。react state是变化的,所以react不是纯函数式。当然高阶抽象这样的良好的编程规范,不是函数式语言所特有的,在我们讲面向对象语言的设计模式的时候,讲:单一原则、里式原则(不记得那些名词了,之后补),这些原则也都在强调一件事,逻辑抽象,代码分层提高代码可复用率。在进行抽象后,在不同的抽象层中连接的问题上,个人觉得fp能更简洁轻便地实现,也就是说fp在抽象上更具有优势 (原因水平有限还说不清,而且这个blog也不是主讲fp与oop的),看我能不能找到些许例子……oop-learn-about-abstract-form-fp
FP vs OOP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 static int compute (int n, Computer computer) { int result = computer.identity(); for (int i = 1 ; i <= n; i++) { result = computer.compute(result, i); } return result; } interface Computer { int compute (int a, int b) ; int identity () ; } static class Adder implements Computer { @Override public int compute (int a, int b) { return a + b; } @Override public int identity () { return 0 ; } } static class Multiplier implements Computer { @Override public int compute (int a, int b) { return a * b; } @Override public int identity () { return 1 ; } } public static void main (String... args) { System.out.println(compute(5 , new Adder())); System.out.println(compute(5 , new Multiplier())); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @FunctionalInterface interface Computer { int compute (int a, int b) ; } static int compute (int n, int identity, IntBinaryOperator computer) { int result = identity; for (int i = 1 ; i <= n; i++) { result = computer.applyAsInt(result, i); } return result; } public static void main (String... args) { System.out.println(compute(5 , 0 , (a, b) -> a + b)); System.out.println(compute(5 , 1 , (a, b) -> a * b)); }
这两个例子恰巧的说明了面向对象语言也可以用来做函数式编程。显而易见的区别是:fp中不存在子类继承父类,再在子类中定义方法的代码结构,减少了代码的冗余度,代码整体观感更精简。
react 函数式的体现
单向数据流和props不可更改 优势:
更容易debug,知道数据的来源
更有效率,让系统知道各部分的边界是什么
根据输入渲染输出
组件可由function定义,并进行组合
react hooks的引入增强了fp 。当然hooks通过不使用各种生命周期函数和this简化了代码 ,但这不是react hooks最神奇的地方,最神奇的地方在于它可以通过custom hook更简便的进一步提取逻辑、进行抽象,减少了hoc,class,function之间的切换 。 从下面这里例子来看,custom hook 替代hoc实现同样的效果,并且在API上理解也更容易(文档里HOC的限制还挺多了,不过我也没怎么用过HOC)。
If the React community embraces [hooks], it will reduce the number of concepts you need to juggle when writing React applications. Hooks let you always use functions instead of having to constantly switch between functions, classes, higher-order components, and render props.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 const CommentListWithSubscription = withSubscription( CommentList, (DataSource) => DataSource.getComments() ); const BlogPostWithSubscription = withSubscription( BlogPost, (DataSource, props) => DataSource.getBlogPost(props.id) ); function withSubscription (WrappedComponent, selectData ) { return class extends React .Component { constructor (props) { super (props); this .handleChange = this .handleChange.bind(this ); this .state = { data: selectData(DataSource, props) }; } componentDidMount() { DataSource.addChangeListener(this .handleChange); } componentWillUnmount() { DataSource.removeChangeListener(this .handleChange); } handleChange() { this .setState({ data: selectData(DataSource, this .props) }); } render() { return <WrappedComponent data={this.state.data} {...this.props} />; } }; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function useData (selectData ) => { const [data,setDate] = useState(); useEffect( function handleChange ( ) { setDate( selectData(DataSource, props) ) } DataSource.addChangeListener(handleChange); return DataSource.removeChangeListener(handleChange) ,[]) return data; } const CommentList = () => { const comments = useData((DataSource ) => DataSource.getComments()) return ( <div> {comments.map((comment ) => ( <Comment comment={comment} key={comment.id} /> ))} </div> ) } const BlogPost = () => { const blogPost = useData((DataSource, props) => DataSource.getBlogPost(props.id)) return <TextBlock text={blogPost} / >;}