Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/raykid/reactnativevisitor

ReactNativeVisitor提供给你对jsx语法产生的、无法修改的对象进行二次修改的能力。
https://github.com/raykid/reactnativevisitor

Last synced: 24 days ago
JSON representation

ReactNativeVisitor提供给你对jsx语法产生的、无法修改的对象进行二次修改的能力。

Awesome Lists containing this project

README

        

# [ReactNativeVisitor](https://github.com/Raykid/ReactNativeVisitor) · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/Raykid/ReactNativeVisitor/blob/master/LICENSE) [![npm version](https://img.shields.io/npm/v/react-native-visitor.svg?style=flat)](https://www.npmjs.com/package/react-native-visitor) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/Raykid/ReactNativeVisitor/pulls)

ReactNativeVisitor提供给你对jsx语法产生的、无法修改的对象进行二次修改的能力。

* **破解jsx对象:** 通过React jsx语法产生的对象其实并不是实际的显示对象,而是一个为下次渲染提供参考的配置对象。而且React在底层通过Object.freeze方法冻结了该对象,使得用户在拿到该对象后便没有任何机会对其进行修改。ReactNativeVisitor提供了用户在实际渲染之前对生成对象进行修改的机会,即某种程度上实现了对React jsx机制的破解。
* **下次渲染前你将可以:**
* 修改节点传入参数;
* 修改节点样式Style;
* 增删移动节点;
* 通过key获取某节点内部任何一个后代节点(无需关心嵌套层级)。
* **此外还友情提供:**
* 对样式Style的嵌套功能支持,类似sass/less的嵌套样式语法,以及样式的递归混合功能;
* 对Typescript的支持。

## Installation

ReactNativeVisitor支持npm安装
```
npm install react-native-visitor
```

## Tutorial

* [生成Visitor](#生成Visitor)
* [获取节点类型](#获取节点类型)
* [获取后代Visitor](#获取后代Visitor)
* [获取ReactNode](#获取ReactNode)
* [获取父节点](#获取父节点)
* [获取子节点列表](#获取子节点列表)
* [Text组件内文本](#Text组件内文本)
* [组件参数](#组件参数)
* [组件样式](#组件样式)
* [子节点API](#子节点API)

你可以发给我一个[Pull Request](https://github.com/Raykid/ReactNativeVisitor/pulls),和我一起完善这个工具。

## 生成Visitor
使用wrapVisitor方法可以很轻松地生成一个访问器对象,仅需在jsx代码外部包裹一层方法调用即可,如下。

```jsx
import { wrapVisitor } from 'react-native-visitor';

const visitor = wrapVisitor()(

Hello ReactNativeVisitor!

);
```
[[返回目录]](#Tutorial)

## 获取节点类型
可以通过Visitor提供的type属性获取到该Visitor对应的节点类型,type是个字符串。

```jsx
const visitor = wrapVisitor()(

Hello ReactNativeVisitor!

);
console.log(visitor.type);// View
console.log(visitor.text.type)// Text
```
[[返回目录]](#Tutorial)

## 获取后代Visitor
你可以通过给后代节点起名字(key)来为Visitor提供访问依据,该功能忽略层级嵌套,如下。

```jsx
const visitor = wrapVisitor()(


Hello ReactNativeVisitor!


);
console.log(visitor.keyDict.depth_0);// 根级别View组件的Visitor
console.log(visitor.depth_1);// 直接访问key的效果与通过keyDict访问相同
console.log(visitor.target_text.children);// Hello ReactNativeVisitor!
```
[[返回目录]](#Tutorial)

## 获取ReactNode
当你需要获取ReactNode对象时,例如在React组件的render方法中需要return时,使用visitor的node属性即可。

```jsx
function render()
{
const visitor = wrapVisitor()(

Hello ReactNativeVisitor!

);
return visitor.node;
}
```
[[返回目录]](#Tutorial)

## 获取父节点
调用Visitor的parent属性即可获取父级Visitor。

```jsx
const visitor = wrapVisitor()(



);
console.log(visitor.subContainer.parent === visitor.parentContainer);// true
```
[[返回目录]](#Tutorial)

## 获取子节点列表
调用Visitor的children属性可以获取容器节点Visitor的所有子节点Visitor数组。

```jsx
const visitor = wrapVisitor()(

第一行文本
第二行文本
第三行文本

);
console.log(visitor.children);// 返回由三个Text类型Visitor组成的数组
```
[[返回目录]](#Tutorial)

## Text组件内文本
和获取子节点数组一样,调用Text组件Visitor的children属性,对于Text组件来说,children返回的是字符串而不是数组。如果想修改文本,只需设置它即可。

```jsx
function render()
{
const visitor = wrapVisitor()(我是文本);
console.log(visitor.children);// 我是文本
visitor.children = "我是新的文本";
return visitor.node;// 界面上将渲染“我是新的文本”这行字
}
```
[[返回目录]](#Tutorial)

## 组件参数
Visitor提供了props属性,以允许你访问和修改组件的传入参数。

```jsx
function render()
{
const visitor = wrapVisitor()();
console.log(visitor.props.prop1);// 这是参数1
visitor.props.prop2 = "这里新增了参数2";
return visitor.node;// MyComp组件将接收到值为"这里新增了参数2"的参数prop2
}
```
[[返回目录]](#Tutorial)

## 组件样式
Visitor提供了style属性,以允许你更加便捷地访问和修改组件的样式。

```jsx
function render()
{
const visitor = wrapVisitor()(
我是一段文字
);
visitor.style.color = "red";
return visitor.node;// 最终“我是一段文字”会被渲染成红色
}
```
在样式方面,ReactNodeVisitor还额外提供了类似于sass/less的完整解决方案,其中包括:
* [创建样式表](#创建样式表)
* [合并样式](#合并样式)
* [交叉合并样式](#交叉合并样式)
* [附加样式](#附加样式)
* [附加内容样式](#附加内容样式)

### 创建样式表
ReactNodeVisitor提供一个createStyleSheet方法,可以替换掉React Native原始的StyleSheet.create方法,用于支持样式多层嵌套功能。
```jsx
import { createStyleSheet } from 'react-native-visitor';

function render()
{
// 声明一个含有任意层级结构的样式表对象
const styles = createStyleSheet({
root: {
width: "100%",
height: "100%",

// 这就是个嵌套的样式结构
text1: {
color: "black",
fontWeight: "500"
},

// 这是第二个嵌套的样式结构
second: {
display: "flex",
justifyContent: "center",
alignItems: "center",

// 它里面还有一层嵌套的样式结构
text2: {
color: "red"
}
}
}
});
// 在书写结构时按照层级结构赋值样式,让结构更清晰
return
文本1

文本2

;
}
```

### 合并样式
mergeStyles方法允许你将多个样式对象进行合并,得出一个样式对象,所有内嵌的同名样式都会***递归***地被合并。如下:
```jsx
import { createStyleSheet, mergeStyles } from 'react-native-visitor';

function render()
{
// 这是第一个样式表
const style1 = createStyleSheet({
root: {
width: "100%",

text: {
fontSize: 30
}
}
});
// 这是第二个样式表
const style2 = createStyleSheet({
root: {
height: 200,

text: {
color: "red"
}
}
});
// 这是合并后的样式表
const mergeRoot = mergeStyles(style1.root, style2.root);
// 使用合并后的样式表书写结构
return
文本
;
// 最后得到的样式表形如
/*
{
root: {
width: "100%",
height: 200,

text: {
fontSize: 30,
color: "red"
}
}
}
*/
}
```

### 交叉合并样式
有这样一个需求,整个页面有正常、正确和错误3个状态,页面中有一个按钮,正常是黄色的。当页面状态为正确时,按钮背景要变成绿色,反之当页面状态为错误时,按钮背景要变成红色。

交叉合并功能就是为了解决这样的问题而诞生的,它模仿的是sass/less中的并列样式功能。

交叉合并用到的方法为crossMergeStyles。下面的例子就说明了crossMergeStyles是如何给一个样式赋上状态的。
```jsx
import { createStyleSheet, crossMergeStyles } from 'react-native-visitor';

function render()
{
const styles = createStyleSheet({
root: {
width: "100%",
height: "100%",

button: {
position: "absolute",
bottom: 100,
width: 200,
height: 100,
backgroundColor: "yellow",
display: "flex",
justifyContent: "center",
alignItems: "center",

text: {
fontSize: 30,
color: "black",
}
},

// 这个是root为正确状态下的样式结构
_right: {
button: {
backgroundColor: "green",

text: {
color: "white"
}
}
},

// 这个是root为错误状态下的样式结构
_wrong: {
button: {
backgroundColor: "red",

text: {
color: "white"
}
}
}
}
});
// 假设有一个isRight变量可以获取到页面状态
// crossMergeStyles方法第二个参数接收一个字符串数组,字符串都是某个样式节点的状态名
// 样式的状态名必须是该层样式的第一级子样式。一般都用下划线作为前缀,标明它是个状态,而不是一个嵌套结构,但这不是必须的,你完全可以自己决定前缀或者完全没有前缀(这将不太好管理)。
const mergeRoot = crossMergeStyles(styles.root, [
isRight && "_right",
!isRight && "_wrong"
]);
return

点我

;
// 这样当isRight为true时,你会看到按钮变成了绿底白字,而当isRight为false时,按钮则变成了红底白字。
}
```
交叉合并样式几乎是开发界面时最常用的方法,因为通常页面或者组件都需要维护多个不同的状态,如果为每个状态都写一个完整的样式结构将会非常麻烦,特别是当样式不支持嵌套时……

### 附加样式
上面几个方法都可以脱离Visitor体系使用。但如果你使用Visitor体系开发,则appendStyles可以让你快速给一个已经有样式的Visitor增加新的样式。
```jsx
import { createStyleSheet, crossMergeStyles, appendStyles } from 'react-native-visitor';

function render()
{
// 假设这是我从其他地方获取到的Visitor
const visitor = wrapVisitor()(

文本

);
// 我希望让Text组件的字体加粗,我可以这么干
// 第一个参数给要附加样式的Visitor,这里是visitor.text
// 第二个参数就是要附加的样式结构
appendStyles(visitor.text, {
fontWeight: "bold"
});
// 当然你也可以使用上面提到的任何方法修改你需要的样式,然后再附加上去
const styles = createStyleSheet({
fontWeight: "bold",

_right: {
color: "green"
},

_wrong: {
color: "red"
}
});
appendStyles(visitor.text, crossMergeStyles(styles, [
isRight && "_right",
!isRight && "_wrong"
]));
// 返回修改后的结构
return visitor.node;
}
```

### 附加内容样式
这个和附加样式差不多,只不过ScrollView系列组件有两个样式(style和contentContainerStyle),appendContentContainerStyles就是针对后者的附加样式。如果你拿到的Visitor是ScrollView、ListView、FlatList,则用这个方法可以给组件内部容器增加样式。

[[返回目录]](#Tutorial)

## 子节点API
通过Visitor提供的子节点API,你可以方便地添加新节点或者移除、移动已有节点。

```jsx
function render()
{
const visitor = wrapVisitor()(

文本1
文本2
文本3

);
// 添加节点,addChild可以直接接收ReactNode,后面它会被转换为子Visitor
visitor.addChild(我是新增的文本5);
// 按索引添加节点,你也可以传递一个Visitor,和直接传递ReactNode是一样的
const insertVisitor = wrapVisitor()(
我是插入的文本4
);
visitor.addChildAt(insertVisitor, 3);
// 移除节点
visitor.removeChild(visitor.text3);
// 替换节点
visitor.replace(我是新的文本2, visitor.text2);
// 返回node
return visitor.node;
// 这样下来实际的显示会和以下代码生成的一样
/*

文本1
我是新的文本2
我是插入的文本4
我是新增的文本5

*/
}
```
### 全部子节点API包括:
```ts
/**
* 查看是否含有某个子节点
*
* @param {ReactNodeVisitor} child 要测试的子节点Visitor
* @returns {boolean} 是否含有该节点
*/
hasChild(child:ReactNodeVisitor):boolean;

/**
* 获取子节点索引
*
* @param {ReactNodeVisitor} child 子节点Visitor
* @returns {number} 子节点索引
*/
getChildIndex(child:ReactNodeVisitor):number;

/**
* 获取指定索引处的子节点Visitor
*
* @param {number} index 指定索引
* @returns {ReactNodeVisitor} 获取到的子节点Visitor
*/
getChildAt(index:number):ReactNodeVisitor;

/**
* 添加显示节点
*
* @param {(ReactNodeVisitor|React.ReactNode)} child 要添加的显示节点,支持ReactNode或Visitor
* @returns {ReactNodeVisitor} 添加的节点Visitor
*/
addChild(child:ReactNodeVisitor|React.ReactNode):ReactNodeVisitor;

/**
* 在指定索引处添加显示节点
*
* @param {(ReactNodeVisitor|React.ReactNode)} child 要添加的显示节点,支持ReactNode或Visitor
* @param {number} index 要添加到的索引
* @returns {ReactNodeVisitor} 添加的节点Visitor
*/
addChildAt(child:ReactNodeVisitor|React.ReactNode, index:number):ReactNodeVisitor;

/**
* 在某个已有子节点前面插入显示节点
*
* @param {(ReactNodeVisitor|React.ReactNode)} child 要添加的显示节点,支持ReactNode或Visitor
* @param {ReactNodeVisitor} refChild 已有节点Visitor
* @returns {ReactNodeVisitor} 添加的节点Visitor
*/
addChildBefore(child:ReactNodeVisitor|React.ReactNode, refChild:ReactNodeVisitor):ReactNodeVisitor;

/**
* 在某个已有子节点后面插入显示节点
*
* @param {(ReactNodeVisitor|React.ReactNode)} child 要添加的显示节点,支持ReactNode或Visitor
* @param {ReactNodeVisitor} refChild 已有节点Visitor
* @returns {ReactNodeVisitor} 添加的节点Visitor
*/
addChildAfter(child:ReactNodeVisitor|React.ReactNode, refChild:ReactNodeVisitor):ReactNodeVisitor;

/**
* 将自身从父容器中移除
*
* @returns {ReactNodeVisitor} 返回被移除的节点Visitor
*/
remove():ReactNodeVisitor;

/**
* 移除一个子节点
*
* @param {ReactNodeVisitor} child 要移除的子节点Visitor
* @returns {ReactNodeVisitor} 被移除的节点Visitor
*/
removeChild(child:ReactNodeVisitor):ReactNodeVisitor;

/**
* 移除指定索引处的子节点
*
* @param {number} index 要移除的子节点索引
* @returns {ReactNodeVisitor} 被移除的节点Visitor
*/
removeChildAt(index:number):ReactNodeVisitor;

/**
* 清空子节点
*
* @returns {ReactNodeVisitor[]} 被移除的字节点Visitor列表
*/
removeChildren():ReactNodeVisitor[];

/**
* 替换子节点
*
* @param {(ReactNodeVisitor|React.ReactNode)} child 要替换成的子节点,支持ReactNode或Visitor
* @param {ReactNodeVisitor} refChild 要被替换的子节点Visitor
* @returns {ReactNodeVisitor} 返回被添加的节点Visitor
*/
replace(child:ReactNodeVisitor|React.ReactNode, refChild:ReactNodeVisitor):ReactNodeVisitor;
```

[[返回目录]](#Tutorial)

### Issues

如果你有任何问题、意见或建议,欢迎给我提[issues](https://github.com/Raykid/ReactNativeVisitor/issues),和我一起完善这个小工具。

### License

ReactNativeVisitor is [MIT licensed](./LICENSE).