50天用react.js重写50个web项目,我学到了什么?

3,239 阅读12分钟

1.Expanding Cards

效果如图所示:

1.png

学到了什么?

  1. React的函数组件中建立数据通信,我们通常使用useState方法。它的使用方式采用数组解构的方式,如下:
const [state,setState] = React.useState(false);//useState方法的参数可以是任意的JavaScript数据类型

解构的第一个参数是我们定义并且访问的数据状态,第二个参数则是当我们需要变动数据状态时所调用的方法,其作用类似类组件中的this.setState

更详细的使用方式参考文档 useState API

2.与类组件类似的钩子函数,或者也可以理解为是函数组件的生命周期useEffect。它接收一个副作用函数effect作为参数,如下所示:

useEffect(() => {});//里面的箭头函数即副作用函数

以上示例只是做了一个简单的更换文档标题,事实上在这个副作用函数中,我们可以做很多事情,详情参考文档 useEffect API

3.React内部有自己的一套事件机制,我们称之为合成事件。它与正常的JavaScript绑定事件很类似,但是它将事件命名方式采用了驼峰式写法,并且它也对事件对象做了一层包装,其名为SyntheticEvent。注意,这是一种跨浏览器端的包装器,我们如果想要使用原生的事件对象,可以使用nativeEvent属性,这在后面的示例中可能会涉及到。比如我们以上的事件绑定:

onClick={ onChangeHandler.bind(this,index) }

注意这里,我们通常需要调用bind方法来将this绑定到该组件实例上,为什么要使用bind方法来绑定this呢,这是因为绑定事件的回调函数(如这里的:onChangeHandler),它是作为一个中间变量的,在调用该回调函数的时候this指向会丢失,所以需要显示的绑定this,这也是受JavaScript自身特性所限制的。详情可参考React绑定this的原因中的解释。与之类似的是在类组件中绑定合成事件,我们也一样需要显示的绑定this指向。

4.map方法的原理。map方法迭代一个数组,然后根据返回值来对数组项做处理,并返回处理后的数组,请注意该方法不会改变原数组。比如:

[1,2,3].map(i => i <= 1);//返回[1]

jsx中渲染列表也正是利用了map方法的这一特性,并且我们需要注意的是渲染列表的时候必须要指定一个key,这是为了方便DIFF算法更好的比对虚拟DOM。

5.React中给标签添加类名,我们是写成className的,这是因为classJavaScript当做关键字。而如果我们需要动态绑定类名,可以看到,我们使用了模板字符串,在这里更像是写JavaScript,比如我们可以利用三元表达式做判断。

6.React中给标签绑定style样式,我们通常可以绑定一个对象,在React中,我们绑定动态数据就是写一对{}花括号,然后style里面的样式我们通常声明成对象来表示,比如:

{
    background:"#fff"
}

这代表它是一个样式对象,然后React会在内部去将样式对象转换成样式字符串,然后添加到DOM的style对象中。

2.Progress Steps

效果如图所示:

2.png

学到了什么?

与第一个示例所用到的知识点很类似,相关的不必做说明。接下来我们来看不一样的。

  1. 父组件向子组件传递数据,我们通常都是使用props。可以看到以上的示例,我们暴露了4个props,即:
width
stepItems
currentActive 
onClickItem

width就是设置步骤条的容器宽度,这个没什么可说的,stepItems就是步骤条的子组件,是一个数组,也可以在数组项中写jsx。而currentActive则是传入的当前是哪一步,是一个索引值,通常应该是数值。至于onClickItem则是子组件暴露给父组件的方法。

  1. 类组件的生命周期。在这个类组件当中,我们使用到了constructor,componentDidMount,render的生命周期钩子函数。我们可以根据语义来推测,当一个类组件被初始化时所经历的生命周期钩子函数执行顺序一定是constructor => render => componentDidMount。从语义上来将constructor是一个构造函数,用于初始化状态,然后初始化完成之后,我们就会渲染组件,然后才是准备挂载组件。

额外的,我们扩展一下,根据文档说明,我们可以知道详细的生命周期。React组件生命周期包含3个阶段:

挂载 => 更新 => 卸载

在组件挂载之前执行的有:

constructor => static getDerivedStateFromProps => render => componentWillMount(即将过时) => componentDidMount

在组件state变更之后,也就是更新之后,执行的有:

static getDerivedStateFromProps => shouldComponentUpdate => render => getSnapshotBeforeUpdate => componentWillReceiveProps(即将过时) => componentWillUpdate(即将过时) => componentDidUpdate

在组件卸载之后,执行的有:

componentWillUnmount

另还有错误处理的生命周期,也就是在渲染过程,生命周期,或子组件的构造函数发生错误时,会执行的钩子函数:

static getDerivedFromStateError => componentDidCatch

这里面有些生命周期我们并没有用到,有些则是几乎涵盖了我们后续的所有示例,所以我们一定要牢记组件的生命周期的顺序。

但是就这个示例而言,我们学会的应该是如何封装一个组件。

3.Rotating Navigation Animation

效果如图所示:

3.png

学到了什么?

一些相同同前面示例相同的知识点自不必说,我们来看不一样的知识点。

1.模块组合导出

//components目录下的index.js文件
export { default as Content } from './Content';
export { default as LeftMenu } from './LeftMenu';
export { default as NavList } from "./NavList";

可以看到,我们可以将组件如上面那样导出,然后我们就可以单独引入一个js文件,再引入相关的组件即可使用。如下:

import { Content, NavList, LeftMenu } from './components/index';

2.react组件如何渲染html字符串

react提供了一个dangerouslySetInnerHTML属性,这个属性的属性值是一个以__html作为属性,值是html字符串的对象,然后,我们就可以将html字符串渲染成真实的DOM元素。如下所示:

<p dangerouslySetInnerHTML={{ __html:"<span>测试代码</span>" }}></p>
//<p><span>测试代码</span></p>

4.hidden-search-widget

效果如图所示:

4.png

学到了什么?

本示例同样的与前面的示例有一样的知识点,相关的不会做说明,只会做不同的知识点介绍,后续的同理。

1.判断数据的类型。利用对象原型链上的toString方法,我们可以得到一个字符串值,这个字符串值的格式类似[object Object]这样,也就是说我们可以通过这个字符串值来判断一个数据的类型。例如:

const getType = v => Object.prototype.toString.call(v).slice(8,-1).toLowerCase();
const isArray = v => getType(v) === "array";
isArray([1,2,3]);//true
isArray("");//false

这里我们应该知道Object.prototype.toString.call(v)返回的就是类似[object Object]这样的值。所以我们通过截取开始索引为8,结束索引为该字符串长度减1,也就是这里的-1,我们就可以获取到第二个值Object,然后再调用toLowerCase()方法来将全部字母转换成小写,然后,我们就可以知道数据的类型了。比如这里的判断是否是数组,那么只需要判断该值是否等于"array"即可。

2.React.createRef API

有时候,我们恰恰需要操作一些原生DOM元素的API,例如这个示例的输入框的关注焦点事件。这时候这个API就有了用武之地,我们相当于使用这个API创建一个与DOM元素通信的桥梁,通过这个访问这个API的实例的current属性,我们就可以访问到相应的DOM元素。

更详细的可以参考文档createRef API

3.如何封装一个input组件。

这个示例也教会了我们如何封装一个input组件。

5.Blurry Loading

效果如图所示:

5.png

学到了什么?

  1. componentDidMount生命周期中创建定时器以及在componentWillUnmount中清除定时器。

  2. 类组件中的this.setState更新状态。该方法接收2个参数,第一个参数则是我们的react状态,它可以是一个函数,也可以是一个对象。第二个参数则是一个回调函数。谈到这里,可能就会提到一个问题,那就是setState到底是异步还是同步? 答案如下:

答:react中的setState在合成事件和钩子函数中是"异步"的,而在原生事件和setTimeout中则是同步的。

之所以是"异步",并不代表在react内部中是"异步"代码实现的,事实上,它仍然是同步执行的一个过程。

只是合成事件和钩子函数的调用顺序在更新之前,导致在合成函数和钩子函数中没法立即拿到更新后的值,所以就形成了所谓的"异步"。

事实上,我们可以通过制定第二个参数即callback(回调函数)来获取到更新后的值。

react.js对setState的源码实现也不是很复杂,它将传入的参数作为值添加到updater也就是更新器的一个定义好的队列(即:enqueueSetState)中。

react中的批量更新优化也是建立在合成事件和钩子函数(也就是"异步")之上的,在原生事件和setTimeout中则不会进行批量更新。

比如在"异步"中对同一个值进行多次setState,依据批量更新则会对其进行策略覆盖,而如果是对不同的多个值setState,则会利用批量更新策略对其进行合并然后批量更新。

更详细的可以参考文档 setState

除此之外,这里也有一个非常重要的工具函数(这在js的实现中也提及过),如下所示:

const scale = (n,inMin,inMax,outerMin,outerMax) => (n - inMin) * (outerMax - outerMin) / (inMax - inMin) + outerMin;

这个工具函数的作用就是将一个范围数字映射到另一个数字范围。比方说,将1 ~ 100的数字范围映射到0 ~ 1之间。 详情

6.Scroll Animation

效果如图所示:

6.png

学到了什么?

1.动态组件

我们通过props传递一个值用来确定组件名。这里以Title组件为例,如下所示:

import React from "react";
const TitleComponent = (props) => {
    let TagName = `h${ props.level }`;
    return (
        <React.Fragment>
            <TagName>{ props.children }</TagName>
        </React.Fragment>
    )
}
export default TitleComponent;

虽然核心代码十分简单,这里需要注意,React组件需要首字母大写,这算是一个约定的规则,其次我们通过props传递一个level用来确定我们使用的是h1~h6哪个来做标签,事实上这里我们应该对level做一个限制,只允许值为1~6

2.Fragment元素

这个元素类似于一个占位符节点,我们知道,当两个元素并列在一个React组件中,是不被允许的,React组件需要提供一个根节点,但有时候,我们不需要一个实际的元素作为根节点包裹它们,所以我们就可以使用Fragment元素来包裹它们。该元素还有一个简写<></>,事实上在Vue2.x中也有这个限制,这是受虚拟DOM DIFF算法所限制的。

更详细的可以见文档Fragment

3.函数防抖

export const throttle = (fn, time = 1000) => {
    let done = false;
    return (...args) => {
        if (!done) {
            fn.call(this, ...args);
            done = true;
        }
        setTimeout(() => {
            done = false;
        }, time)
    }
}

函数防抖与节流可参考 这篇文章

4.监听滚动事件,事实上这里的实现原理同JavaScript实现版本一致,只不过稍微转换一下思维即可。

7. Split Landing Page

效果如图所示:

7.png

学到了什么?

这个示例涉及到的知识点前面的示例都提及过,所以这里不必赘述。

8.Form Wave

效果如图所示:

8.png

学到了什么?

  1. setState更新对象,如果state是一个对象,我们有2种方式来更新。 1.1 利用Object.assign方法来更新。 1.2 直接覆盖整个对象。

以上2种方式伪代码如下:

//  第1种方式
const loginInfo = Object.assign({},this.state.loginInfo,{
   [key]:updateValue
})
this.setState({
   loginInfo
});
// 第2种方式
let { loginInfo } = this.state;
loginInfo[key] = updateValue;
this.setState({ loginInfo });

9.Sound Board

效果如图所示:

9.png

学到了什么?

这个示例涉及到的知识点前面的示例都提及过,所以这里不必赘述。

10. Dad Jokes

效果如图所示:

10.png

学到了什么?

  1. Suspense组件的使用。该组件可以指定一个加载指示器组件,用来实现组件的懒加载。更详细的文档见 Suspense

  2. 接口请求通常都是在componentDidMount钩子函数中完成的。由于不能直接在该钩子函数中更改状态(react.js会给出一个警告)。所以我们需要让接口请求变成异步。

11. Event Keycodes

效果如图所示:

11.png

学到了什么?

这个示例涉及到的知识点前面的示例都提及过,所以这里不必赘述。

12. Faq Collapse

效果如图所示:

12.png

学到了什么?

这个示例涉及到的知识点前面的示例都提及过,所以这里不必赘述。

13. Random Choice Picker

效果如图所示:

13.png

学到了什么?

这个示例涉及到的知识点前面的示例都提及过,所以这里不必赘述。

14. Animated Navigation

效果如图所示:

14.png

学到了什么?

这个示例涉及到的知识点前面的示例都提及过,所以这里不必赘述。

15. Incrementing Counter

效果如图所示:

15.png

学到了什么?

  1. react.js如何更新数组的某一项?在这里我是更新整个数组的,或许这不是一种好的方式。也希望有大佬能提供思路。我的代码如下:
startCounter() {
    const counterList = [...this.state.counterList];
    // https://stackoverflow.com/questions/29537299/react-how-to-update-state-item1-in-state-using-setstate
    // https://stackoverflow.com/questions/26253351/correct-modification-of-state-arrays-in-react-js
    counterList.forEach(counter => {
      const updateCounter = () => {
          const value = counter.value;
          const increment = value / 100;
          if (counter.initValue < value) {
            counter.initValue = Math.ceil(counter.initValue + increment);
            // use setTimeout or setInterval ?
            counter.timer = setTimeout(updateCounter, 60);
          } else {
            counter.initValue = value;
            clearTimeout(counter.timer);
          }
          // update the array,maybe it's not a best way,is there any other way?
          this.setState({ counterList });
      }
      updateCounter();
    })
}

16. Drink Water

效果如图所示:

16.png

学到了什么?

  1. 这里踩了一个坑,如果使用new Array().fill()来初始化状态,会导致意想不到的渲染效果。所以这里直接初始化了所有的数组项。

详见源码

17. movie-app

效果如图所示:

17.png

学到了什么?

这个示例涉及到的知识点前面的示例都提及过,所以这里不必赘述。

PS:这个示例效果由于接口访问受限,需要你懂的访问。

18. background-slider

效果如图所示:

18.png

学到了什么?

这个示例涉及到的知识点前面的示例都提及过,所以这里不必赘述。

19. theme-clock

效果如图所示:

19.png

学到了什么?

  1. 中英文切换是通过定义一个对象来完成的。其它的没什么好说的,都是前面提及过的知识点。

PS:本示例也用到了与示例5一样的工具函数scale函数

20. button-ripple-effect

效果如图所示:

20.png

学到了什么?

  1. 可以通过调用函数来渲染一个组件。

21. drawing-app

效果如图所示:

21.png

学到了什么?

  1. 在react.js中使用ew-color-picker
  2. 这里踩了一个坑,也就是说必须要设置线条的样式。
this.ctx.lineCap = "round";

否则线条的样式不对劲,虽然我也没有搞清楚这里面的原因。毕竟js版本的实现也没有需要显示的设置这个线条的样式。

22. drag-n-drop

效果如图所示:

22.png

学到了什么?

  1. 这里也踩了一个坑, 详见源码注释

23. content-placeholder

效果如图所示:

23.png

学到了什么?

  1. 一些判断react组件的工具函数。如下:
import React from "react";
export function isString(value){
    return typeof value === "string";
}
export function isClassComponent(component) {
    return typeof component === 'function' && !!component.prototype.isReactComponent;
}

export function isFunctionComponent(component) {
    return typeof component === 'function' && String(component).indexOf('return React.createElement') > -1;
}

export function isReactComponent(component) {
    return isClassComponent(component) || isFunctionComponent(component);
}

export function isElement(element) {
    return React.isValidElement(element);
}

export function isDOMTypeElement(element) {
    return isElement(element) && typeof element.type === 'string';
}

export function isCompositeTypeElement(element) {
    return isElement(element) && typeof element.type === 'function';
}
  1. 如何封装一个卡片组件。

24. kinetic-loader

效果如图所示:

24.png

学到了什么?

这个示例涉及到的知识点前面的示例都提及过,所以这里不必赘述。

25. sticky-navbar

效果如图所示:

25.png

学到了什么?

这个示例涉及到的知识点除了一个工具函数,其它的知识点前面的示例都提及过,所以这里不必赘述。

export function marked(template){
    return template.replace(/.+?[\s]/g,v => `<p>${v}</p>`);
}

这个工具函数的作用就是匹配以空格结束的任意字符,然后替换成p标签包裹这些内容。

PS:这里也做了移动端的布局。

26. double-slider

效果如图所示:

26.png

学到了什么?

这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。

27. toast-notification

效果如图所示:

27.png

学到了什么?

  1. 可以利用ReactDOM.render来封装一个函数调用的toast组件。

  2. ReactDOM.unmountComponentAtNode API的用法这个方法会将从DOM中卸载组件,包括事件处理器和state。详见 文档

  3. getDerivedStateFromProps 静态函数。详见 文档

28. github-profiles

效果如图所示:

28.png

学到了什么?

这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。

29. double-click-heart

效果如图所示:

29.png

学到了什么?

  1. 从合成事件对象中读取原生的事件对象。即nativeEvent属性。

30. auto-text-effect

效果如图所示:

30.png

学到了什么?

这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。

31. password generator

效果如图所示:

31.png

学到了什么?

这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。

32. good-cheap-fast

效果如图所示:

32.png

学到了什么?

  1. 如何封装一个switch组件,即开关小组件。

33. notes-app

效果如图所示:

33.png

学到了什么?

这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。

34. animated-countdown

效果如图所示:

34.png

学到了什么?

这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。

35. image-carousel

效果如图所示:

35.png

学到了什么?

这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。

36. hover board

效果如图所示:

36.png

学到了什么?

  1. react.js合成事件中的setState是同步的,所以这里使用原生的监听事件来实现。 详见源码

37. pokedex

效果如图所示:

37.png

学到了什么?

这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。

38. mobile-tab-navigation

效果如图所示:

38.png

学到了什么?

这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。

39. password-strength-background

效果如图所示:

39.png

学到了什么?

  1. 照着 文档 一步步将tailwindcss添加到create-react-app脚手架中。

40. 3D-background-boxes

效果如图所示:

40.png

学到了什么?

这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。

41. Verify Your Account

效果如图所示:

41.png

学到了什么?

这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。

42. live-user-filter

效果如图所示:

42.png

学到了什么?

这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。

43. feedback-ui-design

效果如图所示:

43.png

学到了什么?

这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。

44. custom-range-slider

效果如图所示:

44.png

学到了什么?

这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。

45. netflix-mobile-navigation

效果如图所示:

45.png

学到了什么?

  1. 为如何实现一个递归组件提供了思路。

46. quiz-app

效果如图所示:

46.png

学到了什么?

这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。

47. testimonial-box-switcher

效果如图所示:

47.png

学到了什么?

这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。

48. random-image-feed

效果如图所示:

48.png

学到了什么?

  1. 实现一个简易版本的预览图片。

49. todo-list

效果如图所示:

49.png

学到了什么?

这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。

50. insect-catch-game

效果如图所示:

50.png

学到了什么?

这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。

特别说明:这个示例算是一个比较综合的示例了。