每天对自己多问几个为什么,总是有着想象不到的收获。 一个菜鸟小白的成长之路(copyer)
在react中 state 和 props是两个非常重要的属性,并且常用的属性。简单来说:都是用来保存数据状态的。
需注意的是state,props的每一次改动都会使页面重新渲染一次。
state的解释
state
意为 状态,组件内部的状态。
state
是一个可变的属性,控制组件内部的状态
state
一般用于组件内部的状态维护,更新组建内部的数据,状态,更新子组件的props等
props的解释
Props
是Properties
的简写。
Props
用于组件之间的通信,一种数据传递的方式。由于React的数据流是自上而下的,所以是从父组件向子组件进行传递。
props
是只读属性。想要修改props,只能从父组件修改,然后传递给子组件。
props的形成
都知道, react 中的 jsx
会通过 babel
转化成 createElement
函数。所以,这里主要就是要知道createElement这个函数。话不多说,先看实例,然后看createElement的源码。
jsx转化成createElement的实例(babel官网的转化)
根据上面的转化和图解:
可以看,createElement
接受多个参数,
第一个参数为组件的名称(大写字母
就是组件的名称,小写字母
开头,就是原生的html标签),
第二个参数为props,如果没有props,就是为 null
第三个参数及跟多的参数(可选参数):
- 如果没有子元素,就是
undefined
- 如果有子元素,那么
子元素的createElement的函数
就作为参数,如果有多个子元素,就依次排列下去(跟vue3的h函数
有点区别,h函数的三个参数是数组,存放子元素的h函数)
看完了上面的分析,是不是对React.createElement函数有点认识了啊,那么接下来看看源码是怎么实现的吧。(这个函数不复杂,但是我还是省略一些代码,便于理解)
const RESERVED_PROPS = { // 处理props, 排除特殊的关键字 key ref 等等
key: true,
ref: true,
__self: true,
__source: true,
};
export function createElement(type, config, children) {
let propName;
const props = {}; // 定义一个props的空对象
//... 省略代码
// 遍历 config 赋值给 props
for (propName in config) {
if (
hasOwnProperty.call(config, propName) && // propsName在config中
!RESERVED_PROPS.hasOwnProperty(propName) // propsName不是关键词
) {
props[propName] = config[propName];
}
}
}
// 获取第二个参数后面的参数个数
const childrenLength = arguments.length - 2;
if (childrenLength === 1) { // 如果长度为1,就是一个ReactElement
props.children = children;
} else if (childrenLength > 1) { // 如果长度为1, 就是一个数组,保存着多个ReactElement
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
// ...
props.children = childArray;
}
return ReactElement( // 生成一个ReactElement的虚拟DOM
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
看望上面的代码,就可以很清楚的了解props的组成。
let props = {}
props是一个对象propName in config
循环createElement的第二个参数config,第二个参数,就是组件中写的propsprops.children
就是组件中是否存在子元素。如果没有子元素,值就是undefined
;如果是有一个子元素,值就是ReactElement元素
;如果有多个,值就是一个数组,数组中存着ReactElement元素
。
props.children
上面的源码解析中已经中分析了,可以继续看上面的分析
defaultProps
在定义中组件的时候,我们可以给组件的props设置默认值
,可以有效的防止没有传递对应的props,是程序报错
。
具体用法:
// 定义组件
const Test = (props) => {
const { title } = props
return (
<div>{title}</div>
)
}
Test.defaultProps = {
title: 'james'
}
//使用组件
const App = () => {
return (
<div>
<Test title="kobe" />
</div>
)
}
在上面使用Test组件的时候,如果传递了title,那么Test中,title就是kobe
。如果没有传递title,那么组件中的title就是james
。
源码分析
createElement中的一个参数,就是组件的名称(type)
// 源码部分处理默认值
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) { // 循环默认props,对象
if (props[propName] === undefined) { // 如果在props中的值为undefined,使用默认值
props[propName] = defaultProps[propName];
}
}
}
displayName
displayName:就是组件取个名字,便于区分。
function Test() {
}
Test.displayName = 'OwnTest' // OwnTest用于展示
用途:
- 在react的设置
设计模式中的组合模式
,就需要通过判断传递的子组件来进行渲染。 - 调试工具的调试的名称显示
PropTypes
对组件的props的类型限定。如果JavaScript是个弱语言,会自动进行隐式转化,容易造成react的程序报错。所以,我们就需要对类型进行限定。
所以,prop-types
就很好对组件的类名进行限定。
安装:
npm install --save prop-types
官网地址:
https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html#gatsby-focus-wrapper
示例:
import PropTypes from 'prop-types';
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
Greeting.propTypes = {
name: PropTypes.string
};
类型限定:
import PropTypes from 'prop-types';
MyComponent.propTypes = {
// 你可以将属性声明为 JS 原生类型,默认情况下
// 这些属性都是可选的。
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// 任何可被渲染的元素(包括数字、字符串、元素或数组)
// (或 Fragment) 也包含这些类型。
optionalNode: PropTypes.node,
// 一个 React 元素。
optionalElement: PropTypes.element,
// 一个 React 元素类型(即,MyComponent)。
optionalElementType: PropTypes.elementType,
// 你也可以声明 prop 为类的实例,这里使用
// JS 的 instanceof 操作符。
optionalMessage: PropTypes.instanceOf(Message),
// 你可以让你的 prop 只能是特定的值,指定它为
// 枚举类型。
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
// 一个对象可以是几种类型中的任意一个类型
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 可以指定一个数组由某一类型的元素组成
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
// 可以指定一个对象由某一类型的值组成
optionalObjectOf: PropTypes.objectOf(PropTypes.number),
// 可以指定一个对象由特定的类型值组成
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
}),
// An object with warnings on extra properties
optionalObjectWithStrictShape: PropTypes.exact({
name: PropTypes.string,
quantity: PropTypes.number
}),
// 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
// 这个 prop 没有被提供时,会打印警告信息。
requiredFunc: PropTypes.func.isRequired,
// 任意类型的必需数据
requiredAny: PropTypes.any.isRequired,
// 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
// 请不要使用 `console.warn` 或抛出异常,因为这在 `oneOfType` 中不会起作用。
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},
// 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。
// 它应该在验证失败时返回一个 Error 对象。
// 验证器将验证数组或对象中的每个值。验证器的前两个参数
// 第一个是数组或对象本身
// 第二个是他们当前的键。
customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
};
哈哈哈,上面的有点多,不用记太多,记住常用的就行了。如果有特殊的需求,就再去查官网吧。
TypeScript对props的限定
在上面,就是对props的类型限定和默认值的设置,在ts中就能完美的解决。
类组件
React.Component<P, S>
P: 就是对props的类型进行限定。
S:就是对state的类型限定
// node_modules/@types/react/index.d.ts
class Component<P, S> {
//设置静态属性
static contextType?: Context<any>;
context: any;
//限定props的类型,为readonly类型,只读的
constructor(props: Readonly<P>);
constructor(props: P, context?: any);
setState<K extends keyof S>(
state: ((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
callback?: () => void
): void;
forceUpdate(callback?: () => void): void;
//render的类型
render(): ReactNode;
//对props的类型限定
readonly props: Readonly<P> & Readonly<{ children?: ReactNode }>;
//state的类型限定
state: Readonly<S>;
refs: {
[key: string]: ReactInstance
};
}
函数组件
React.FC<IProps>
IProps: 就是对props的类型进行限定
type FC<P = {}> = FunctionComponent<P>;
interface FunctionComponent<P = {}> {
//对props进行限定, 返回值是一个ReactElement对象,为了生成虚拟DOM
(props: PropsWithChildren<P>, context?: any): ReactElement | null;
propTypes?: WeakValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
props的类型 :PropsWithChildren<P>
这里PropsWithChildren中内部实现了props新增children属性。
总结
目前对props的所有认识,就在这里,以后遇到新的知识,然后在进行补充。如果上面的有错误,可以提出来哟。
附加信息:createElement的完整源码
/**
* Create and return a new ReactElement of the given type.
* See https://reactjs.org/docs/react-api.html#createelement
*/
export function createElement(type, config, children) {
let propName;
// Reserved names are extracted
const props = {};
let key = null;
let ref = null;
let self = null;
let source = null;
if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
if (__DEV__) {
warnIfStringRefCannotBeAutoConverted(config);
}
}
if (hasValidKey(config)) {
key = '' + config.key;
}
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// Remaining properties are added to a new props object
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName) // reserved_props
) {
props[propName] = config[propName];
}
}
}
// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
if (__DEV__) {
if (Object.freeze) {
Object.freeze(childArray);
}
}
props.children = childArray;
}
// Resolve default props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
if (__DEV__) {
if (key || ref) {
const displayName =
typeof type === 'function'
? type.displayName || type.name || 'Unknown'
: type;
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}
}
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}