react in patterns

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) // 从右向左找第一个小于x的数
j--;
if(i < j)
s[i++] = s[j];
while(i < j && s[i]< x) // 从左向右找第一个大于等于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定义,并进行组合

why react hooks

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} />;
}
0%