50天用react.js重写50个web项目,我学到了什么?
链接:https://segmentfault.com/a/1190000040813435
1.Expanding Cards
效果如图所示:
-
源码 -
在线示例
学到了什么?
-
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
的,这是因为class
被JavaScript
当做关键字。而如果我们需要动态绑定类名,可以看到,我们使用了模板字符串,在这里更像是写JavaScript,比如我们可以利用三元表达式做判断。
6.React中给标签绑定style样式,我们通常可以绑定一个对象,在React中,我们绑定动态数据就是写一对{}
花括号,然后style里面的样式我们通常声明成对象来表示,比如:
{
background:"#fff"
}
这代表它是一个样式对象,然后React会在内部去将样式对象转换成样式字符串,然后添加到DOM的style对象中。
2.Progress Steps
效果如图所示:
-
源码 -
在线示例
学到了什么?
与第一个示例所用到的知识点很类似,相关的不必做说明。接下来我们来看不一样的。
-
父组件向子组件传递数据,我们通常都是使用 props
。可以看到以上的示例,我们暴露了4个props,即:
width
stepItems
currentActive
onClickItem
width就是设置步骤条的容器宽度,这个没什么可说的,stepItems就是步骤条的子组件,是一个数组,也可以在数组项中写jsx。而currentActive
则是传入的当前是哪一步,是一个索引值,通常应该是数值。至于onClickItem
则是子组件暴露给父组件的方法。
-
类组件的生命周期。在这个类组件当中,我们使用到了 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
效果如图所示:
-
源码 -
在线示例
学到了什么?
一些相同同前面示例相同的知识点自不必说,我们来看不一样的知识点。
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
效果如图所示:
-
源码 -
在线示例
学到了什么?
本示例同样的与前面的示例有一样的知识点,相关的不会做说明,只会做不同的知识点介绍,后续的同理。
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
效果如图所示:
-
源码 -
在线示例
学到了什么?
-
在 componentDidMount
生命周期中创建定时器以及在componentWillUnmount
中清除定时器。 -
类组件中的 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
效果如图所示:
-
源码 -
在线示例
学到了什么?
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
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点前面的示例都提及过,所以这里不必赘述。
8.Form Wave
效果如图所示:
-
源码 -
在线示例
学到了什么?
-
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
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点前面的示例都提及过,所以这里不必赘述。
10. Dad Jokes
效果如图所示:
-
源码 -
在线示例
学到了什么?
-
Suspense组件的使用。该组件可以指定一个加载指示器组件,用来实现组件的懒加载。更详细的文档见 Suspense 。 -
接口请求通常都是在componentDidMount钩子函数中完成的。由于不能直接在该钩子函数中更改状态(react.js会给出一个警告)。所以我们需要让接口请求变成异步。
11. Event Keycodes
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点前面的示例都提及过,所以这里不必赘述。
12. Faq Collapse
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点前面的示例都提及过,所以这里不必赘述。
13. Random Choice Picker
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点前面的示例都提及过,所以这里不必赘述。
14. Animated Navigation
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点前面的示例都提及过,所以这里不必赘述。
15. Incrementing Counter
效果如图所示:
-
源码 -
在线示例
学到了什么?
-
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
效果如图所示:
-
源码 -
在线示例
学到了什么?
-
这里踩了一个坑,如果使用new Array().fill()来初始化状态,会导致意想不到的渲染效果。所以这里直接初始化了所有的数组项。
详见源码 。
17. movie-app
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点前面的示例都提及过,所以这里不必赘述。
PS:这个示例效果由于接口访问受限,需要你懂的访问。
18. background-slider
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点前面的示例都提及过,所以这里不必赘述。
19. theme-clock
效果如图所示:
-
源码 -
在线示例
学到了什么?
-
中英文切换是通过定义一个对象来完成的。其它的没什么好说的,都是前面提及过的知识点。
PS:本示例也用到了与示例5一样的工具函数
scale
函数
20. button-ripple-effect
效果如图所示:
-
源码 -
在线示例
学到了什么?
-
可以通过调用函数来渲染一个组件。
21. drawing-app
效果如图所示:
-
源码 -
在线示例
学到了什么?
-
在react.js中使用 ew-color-picker
。 -
这里踩了一个坑,也就是说必须要设置线条的样式。
this.ctx.lineCap = "round";
否则线条的样式不对劲,虽然我也没有搞清楚这里面的原因。毕竟js版本的实现也没有需要显示的设置这个线条的样式。
22. drag-n-drop
效果如图所示:
-
源码 -
在线示例
学到了什么?
-
这里也踩了一个坑, 详见源码注释 。
23. content-placeholder
效果如图所示:
-
源码 -
在线示例
学到了什么?
-
一些判断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';
}
-
如何封装一个卡片组件。
24. kinetic-loader
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点前面的示例都提及过,所以这里不必赘述。
25. sticky-navbar
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点除了一个工具函数,其它的知识点前面的示例都提及过,所以这里不必赘述。
export function marked(template){
return template.replace(/.+?[\s]/g,v => `<p>${v}</p>`);
}
这个工具函数的作用就是匹配以空格结束的任意字符,然后替换成p标签包裹这些内容。
PS:这里也做了移动端的布局。
26. double-slider
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。
27. toast-notification
效果如图所示:
-
源码 -
在线示例
学到了什么?
-
可以利用 ReactDOM.render
来封装一个函数调用的toast
组件。 -
ReactDOM.unmountComponentAtNode API
的用法这个方法会将从DOM中卸载组件,包括事件处理器和state。详见 文档 。 -
getDerivedStateFromProps 静态函数。详见 文档。
28. github-profiles
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。
29. double-click-heart
效果如图所示:
-
源码 -
在线示例
学到了什么?
-
从合成事件对象中读取原生的事件对象。即 nativeEvent
属性。
30. auto-text-effect
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。
31. password generator
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。
32. good-cheap-fast
效果如图所示:
-
源码 -
在线示例
学到了什么?
-
如何封装一个switch组件,即开关小组件。
33. notes-app
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。
34. animated-countdown
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。
35. image-carousel
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。
36. hover board
效果如图所示:
-
源码 -
在线示例
学到了什么?
-
react.js合成事件中的setState是同步的,所以这里使用原生的监听事件来实现。详见源码 。
37. pokedex
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。
38. mobile-tab-navigation
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。
39. password-strength-background
效果如图所示:
-
源码 -
在线示例
学到了什么?
-
照着 文档 一步步将 tailwindcss
添加到create-react-app
脚手架中。
40. 3D-background-boxes
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。
41. Verify Your Account
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。
42. live-user-filter
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。
43. feedback-ui-design
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。
44. custom-range-slider
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。
45. netflix-mobile-navigation
效果如图所示:
-
源码 -
在线示例
学到了什么?
-
为如何实现一个递归组件提供了思路。
46. quiz-app
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。
47. testimonial-box-switcher
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。
48. random-image-feed
效果如图所示:
-
源码 -
在线示例
学到了什么?
-
实现一个简易版本的预览图片。
49. todo-list
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。
50. insect-catch-game
效果如图所示:
-
源码 -
在线示例
学到了什么?
这个示例涉及到的知识点,前面的示例都提及过,所以这里不必赘述。
特别说明:这个示例算是一个比较综合的示例了。