ReactNativeVisitor ·

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
你可以发给我一个Pull Request,和我一起完善这个工具。
生成Visitor
使用wrapVisitor方法可以很轻松地生成一个访问器对象,仅需在jsx代码外部包裹一层方法调用即可,如下。
import wrapVisitor from 'react-native-visitor'; const visitor = <View> <Text>Hello ReactNativeVisitor!</Text> </View>;
获取节点类型
可以通过Visitor提供的type属性获取到该Visitor对应的节点类型,type是个字符串。
const visitor = <View> <Text ="text">Hello ReactNativeVisitor!</Text> </View>;console;// Viewconsole// Text
获取后代Visitor
你可以通过给后代节点起名字(key)来为Visitor提供访问依据,该功能忽略层级嵌套,如下。
const visitor = <View ="depth_0"> <View ="depth_1"> <Text ="target_text">Hello ReactNativeVisitor!</Text> </View> </View>;console;// 根级别View组件的Visitorconsole;// 直接访问key的效果与通过keyDict访问相同console;// Hello ReactNativeVisitor!
获取ReactNode
当你需要获取ReactNode对象时,例如在React组件的render方法中需要return时,使用visitor的node属性即可。
{ const visitor = <View> <Text>Hello ReactNativeVisitor!</Text> </View> ; return visitornode;}
获取父节点
调用Visitor的parent属性即可获取父级Visitor。
const visitor = <View ="parentContainer"> <View ="subContainer"/> </View>;console;// true
获取子节点列表
调用Visitor的children属性可以获取容器节点Visitor的所有子节点Visitor数组。
const visitor = <View> <Text>第一行文本</Text> <Text>第二行文本</Text> <Text>第三行文本</Text> </View>;console;// 返回由三个Text类型Visitor组成的数组
Text组件内文本
和获取子节点数组一样,调用Text组件Visitor的children属性,对于Text组件来说,children返回的是字符串而不是数组。如果想修改文本,只需设置它即可。
{ const visitor = <Text>我是文本</Text>; console;// 我是文本 visitorchildren = "我是新的文本"; return visitornode;// 界面上将渲染“我是新的文本”这行字}
组件参数
Visitor提供了props属性,以允许你访问和修改组件的传入参数。
{ const visitor = <MyComp ="这是参数1"></MyComp>; console;// 这是参数1 visitorpropsprop2 = "这里新增了参数2"; return visitornode;// MyComp组件将接收到值为"这里新增了参数2"的参数prop2}
组件样式
Visitor提供了style属性,以允许你更加便捷地访问和修改组件的样式。
{ const visitor = <Text =>我是一段文字</Text> ; visitorstylecolor = "red"; return visitornode;// 最终“我是一段文字”会被渲染成红色}
在样式方面,ReactNodeVisitor还额外提供了类似于sass/less的完整解决方案,其中包括:
创建样式表
ReactNodeVisitor提供一个createStyleSheet方法,可以替换掉React Native原始的StyleSheet.create方法,用于支持样式多层嵌套功能。
import createStyleSheet from 'react-native-visitor'; { // 声明一个含有任意层级结构的样式表对象 const styles = ; // 在书写结构时按照层级结构赋值样式,让结构更清晰 return <View => <Text =>文本1</Text> <View => <Text =>文本2</Text> </View> </View>;}
合并样式
mergeStyles方法允许你将多个样式对象进行合并,得出一个样式对象,所有内嵌的同名样式都会***递归***地被合并。如下:
import createStyleSheet mergeStyles from 'react-native-visitor'; { // 这是第一个样式表 const style1 = ; // 这是第二个样式表 const style2 = ; // 这是合并后的样式表 const mergeRoot = ; // 使用合并后的样式表书写结构 return <View => <Text =>文本</Text> </View>; // 最后得到的样式表形如 /* { root: { width: "100%", height: 200, text: { fontSize: 30, color: "red" } } } */}
交叉合并样式
有这样一个需求,整个页面有正常、正确和错误3个状态,页面中有一个按钮,正常是黄色的。当页面状态为正确时,按钮背景要变成绿色,反之当页面状态为错误时,按钮背景要变成红色。
交叉合并功能就是为了解决这样的问题而诞生的,它模仿的是sass/less中的并列样式功能。
交叉合并用到的方法为crossMergeStyles。下面的例子就说明了crossMergeStyles是如何给一个样式赋上状态的。
import createStyleSheet crossMergeStyles from 'react-native-visitor'; { const styles = ; // 假设有一个isRight变量可以获取到页面状态 // crossMergeStyles方法第二个参数接收一个字符串数组,字符串都是某个样式节点的状态名 // 样式的状态名必须是该层样式的第一级子样式。一般都用下划线作为前缀,标明它是个状态,而不是一个嵌套结构,但这不是必须的,你完全可以自己决定前缀或者完全没有前缀(这将不太好管理)。 const mergeRoot = ; return <View => <View => <Text =>点我</Text> </View> </View>; // 这样当isRight为true时,你会看到按钮变成了绿底白字,而当isRight为false时,按钮则变成了红底白字。}
交叉合并样式几乎是开发界面时最常用的方法,因为通常页面或者组件都需要维护多个不同的状态,如果为每个状态都写一个完整的样式结构将会非常麻烦,特别是当样式不支持嵌套时……
附加样式
上面几个方法都可以脱离Visitor体系使用。但如果你使用Visitor体系开发,则appendStyles可以让你快速给一个已经有样式的Visitor增加新的样式。
import createStyleSheet crossMergeStyles appendStyles from 'react-native-visitor'; { // 假设这是我从其他地方获取到的Visitor const visitor = <View> <Text ="text" =>文本</Text> </View> ; // 我希望让Text组件的字体加粗,我可以这么干 // 第一个参数给要附加样式的Visitor,这里是visitor.text // 第二个参数就是要附加的样式结构 ; // 当然你也可以使用上面提到的任何方法修改你需要的样式,然后再附加上去 const styles = ; ; // 返回修改后的结构 return visitornode;}
附加内容样式
这个和附加样式差不多,只不过ScrollView系列组件有两个样式(style和contentContainerStyle),appendContentContainerStyles就是针对后者的附加样式。如果你拿到的Visitor是ScrollView、ListView、FlatList,则用这个方法可以给组件内部容器增加样式。
子节点API
通过Visitor提供的子节点API,你可以方便地添加新节点或者移除、移动已有节点。
{ const visitor = <View> <Text ="text1">文本1</Text> <Text ="text2">文本2</Text> <Text ="text3">文本3</Text> </View> ; // 添加节点,addChild可以直接接收ReactNode,后面它会被转换为子Visitor visitor; // 按索引添加节点,你也可以传递一个Visitor,和直接传递ReactNode是一样的 const insertVisitor = <Text ="text4">我是插入的文本4</Text> ; visitor; // 移除节点 visitor; // 替换节点 visitor; // 返回node return visitornode; // 这样下来实际的显示会和以下代码生成的一样 /* <View> <Text key="text1">文本1</Text> <Text>我是新的文本2</Text> <Text key="text4">我是插入的文本4</Text> <Text key="text5">我是新增的文本5</Text> </View> */}
全部子节点API包括:
/** * 查看是否含有某个子节点 * * @param * @returns */hasChildchild:ReactNodeVisitor:boolean; /** * 获取子节点索引 * * @param * @returns */getChildIndexchild:ReactNodeVisitor:number; /** * 获取指定索引处的子节点Visitor * * @param * @returns */getChildAtindex:number:ReactNodeVisitor; /** * 添加显示节点 * * @param * @returns */addChildchild:ReactNodeVisitor|React.ReactNode:ReactNodeVisitor; /** * 在指定索引处添加显示节点 * * @param * @param * @returns */addChildAtchild:ReactNodeVisitor|React.ReactNode, index:number:ReactNodeVisitor; /** * 在某个已有子节点前面插入显示节点 * * @param * @param * @returns */addChildBeforechild:ReactNodeVisitor|React.ReactNode, refChild:ReactNodeVisitor:ReactNodeVisitor; /** * 在某个已有子节点后面插入显示节点 * * @param * @param * @returns */addChildAfterchild:ReactNodeVisitor|React.ReactNode, refChild:ReactNodeVisitor:ReactNodeVisitor; /** * 将自身从父容器中移除 * * @returns */remove:ReactNodeVisitor; /** * 移除一个子节点 * * @param * @returns */removeChildchild:ReactNodeVisitor:ReactNodeVisitor; /** * 移除指定索引处的子节点 * * @param * @returns */removeChildAtindex:number:ReactNodeVisitor; /** * 清空子节点 * * @returns */removeChildren:ReactNodeVisitor; /** * 替换子节点 * * @param * @param * @returns */replacechild:ReactNodeVisitor|React.ReactNode, refChild:ReactNodeVisitor:ReactNodeVisitor;
Issues
如果你有任何问题、意见或建议,欢迎给我提issues,和我一起完善这个小工具。
License
ReactNativeVisitor is MIT licensed.