github.com/jgarto/itcv@v0.0.0-20180826224514-4eea09c1aa0d/react.go (about) 1 // Copyright (c) 2016 Paul Jolly <paul@myitcv.org.uk>, all rights reserved. 2 // Use of this document is governed by a license found in the LICENSE document. 3 4 /* 5 6 Package react is a set of GopherJS bindings for Facebook's React, a Javascript 7 library for building user interfaces. 8 9 For more information see https://github.com/myitcv/react/wiki 10 11 */ 12 package react // import "myitcv.io/react" 13 14 //go:generate cssGen 15 //go:generate coreGen 16 17 import ( 18 "fmt" 19 "reflect" 20 21 "honnef.co/go/js/dom" 22 23 "github.com/gopherjs/gopherjs/js" 24 "github.com/gopherjs/jsbuiltin" 25 26 // imported for the side effect of bundling react 27 // build tags control whether this actually includes 28 // js files or not 29 _ "myitcv.io/react/internal/bundle" 30 "myitcv.io/react/internal/core" 31 ) 32 33 const ( 34 reactCompProps = "props" 35 reactCompLastState = "__lastState" 36 reactComponentBuilder = "__componentBuilder" 37 reactCompDisplayName = "displayName" 38 reactCompSetState = "setState" 39 reactCompForceUpdate = "forceUpdate" 40 reactCompState = "state" 41 reactCompGetInitialState = "getInitialState" 42 reactCompShouldComponentUpdate = "shouldComponentUpdate" 43 reactCompComponentDidMount = "componentDidMount" 44 reactCompComponentWillReceiveProps = "componentWillReceiveProps" 45 reactCompComponentWillMount = "componentWillMount" 46 reactCompComponentWillUnmount = "componentWillUnmount" 47 reactCompRender = "render" 48 49 reactCreateElement = "createElement" 50 reactCreateClass = "createClass" 51 reactDOMRender = "render" 52 53 nestedChildren = "_children" 54 nestedProps = "_props" 55 nestedState = "_state" 56 nestedComponentWrapper = "__ComponentWrapper" 57 ) 58 59 var react = js.Global.Get("React") 60 var reactDOM = js.Global.Get("ReactDOM") 61 var object = js.Global.Get("Object") 62 var symbolFragment = react.Get("Fragment") 63 64 // ComponentDef is embedded in a type definition to indicate the type is a component 65 type ComponentDef struct { 66 elem *js.Object 67 } 68 69 var compMap = make(map[reflect.Type]*js.Object) 70 71 // S is the React representation of a string 72 type S = core.S 73 74 func Sprintf(format string, args ...interface{}) S { 75 return S(fmt.Sprintf(format, args...)) 76 } 77 78 type elementHolder = core.ElementHolder 79 80 type Element = core.Element 81 82 type Component interface { 83 RendersElement() Element 84 } 85 86 type componentWithWillMount interface { 87 Component 88 ComponentWillMount() 89 } 90 91 type componentWithDidMount interface { 92 Component 93 ComponentDidMount() 94 } 95 96 type componentWithWillReceiveProps interface { 97 Component 98 ComponentWillReceivePropsIntf(i interface{}) 99 } 100 101 type componentWithGetInitialState interface { 102 Component 103 GetInitialStateIntf() State 104 } 105 106 type componentWithWillUnmount interface { 107 Component 108 ComponentWillUnmount() 109 } 110 111 type Props interface { 112 IsProps() 113 EqualsIntf(v Props) bool 114 } 115 116 type State interface { 117 IsState() 118 EqualsIntf(v State) bool 119 } 120 121 func (c ComponentDef) Props() Props { 122 return *(unwrapValue(c.elem.Get(reactCompProps).Get(nestedProps)).(*Props)) 123 } 124 125 func (c ComponentDef) Children() []Element { 126 v := c.elem.Get(reactCompProps).Get(nestedChildren) 127 128 if v == js.Undefined { 129 return nil 130 } 131 132 return *(unwrapValue(v).(*[]Element)) 133 } 134 135 func (c ComponentDef) SetState(i State) { 136 rs := c.elem.Get(reactCompState) 137 is := rs.Get(nestedState) 138 139 cur := *(unwrapValue(is.Get(reactCompLastState)).(*State)) 140 141 if i.EqualsIntf(cur) { 142 return 143 } 144 145 is.Set(reactCompLastState, wrapValue(&i)) 146 c.elem.Call(reactCompForceUpdate) 147 } 148 149 func (c ComponentDef) State() State { 150 rs := c.elem.Get(reactCompState) 151 is := rs.Get(nestedState) 152 153 cur := *(unwrapValue(is.Get(reactCompLastState)).(*State)) 154 155 return cur 156 } 157 158 func (c ComponentDef) ForceUpdate() { 159 c.elem.Call(reactCompForceUpdate) 160 } 161 162 type ComponentBuilder func(elem ComponentDef) Component 163 164 func CreateElement(buildCmp ComponentBuilder, newprops Props, children ...Element) Element { 165 cmp := buildCmp(ComponentDef{}) 166 typ := reflect.TypeOf(cmp) 167 168 comp, ok := compMap[typ] 169 if !ok { 170 comp = buildReactComponent(typ, buildCmp) 171 compMap[typ] = comp 172 } 173 174 propsWrap := object.New() 175 if newprops != nil { 176 propsWrap.Set(nestedProps, wrapValue(&newprops)) 177 } 178 179 if children != nil { 180 propsWrap.Set(nestedChildren, wrapValue(&children)) 181 } 182 183 args := []interface{}{comp, propsWrap} 184 185 for _, v := range children { 186 args = append(args, v) 187 } 188 189 return &elementHolder{ 190 Elem: react.Call(reactCreateElement, args...), 191 } 192 } 193 194 func createElement(cmp interface{}, props interface{}, children ...Element) Element { 195 args := []interface{}{cmp, props} 196 197 for _, v := range children { 198 args = append(args, v) 199 } 200 201 return &elementHolder{ 202 Elem: react.Call(reactCreateElement, args...), 203 } 204 } 205 206 func buildReactComponent(typ reflect.Type, builder ComponentBuilder) *js.Object { 207 compDef := object.New() 208 compDef.Set(reactCompDisplayName, fmt.Sprintf("%v(%v)", typ.Name(), typ.PkgPath())) 209 compDef.Set(reactComponentBuilder, builder) 210 211 compDef.Set(reactCompGetInitialState, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} { 212 elem := this 213 cmp := builder(ComponentDef{elem: elem}) 214 215 var wv *js.Object 216 217 res := object.New() 218 is := object.New() 219 220 if cmp, ok := cmp.(componentWithGetInitialState); ok { 221 x := cmp.GetInitialStateIntf() 222 wv = wrapValue(&x) 223 } 224 225 res.Set(nestedState, is) 226 is.Set(reactCompLastState, wv) 227 228 return res 229 })) 230 231 compDef.Set(reactCompShouldComponentUpdate, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} { 232 var nextProps Props 233 var curProps Props 234 235 // whether a component should update is only a function of its props 236 // ... and a component does not need to have props 237 // 238 // the only way we have of determining that here is whether the this 239 // object has a props property that has a non-nil nestedProps property 240 241 if this != nil { 242 if p := this.Get(reactCompProps); p != nil { 243 if ok, err := jsbuiltin.In(nestedProps, p); err == nil && ok { 244 if v := (p.Get(nestedProps)); v != nil { 245 curProps = *(unwrapValue(v).(*Props)) 246 } 247 } else { 248 return false 249 } 250 } 251 } 252 253 if arguments[0] != nil { 254 if ok, err := jsbuiltin.In(nestedProps, arguments[0]); err == nil && ok { 255 nextProps = *(unwrapValue(arguments[0].Get(nestedProps)).(*Props)) 256 } 257 } 258 259 return !curProps.EqualsIntf(nextProps) 260 })) 261 262 compDef.Set(reactCompComponentDidMount, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} { 263 elem := this 264 cmp := builder(ComponentDef{elem: elem}) 265 266 if cmp, ok := cmp.(componentWithDidMount); ok { 267 cmp.ComponentDidMount() 268 } 269 270 return nil 271 })) 272 273 compDef.Set(reactCompComponentWillReceiveProps, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} { 274 elem := this 275 cmp := builder(ComponentDef{elem: elem}) 276 277 if cmp, ok := cmp.(componentWithWillReceiveProps); ok { 278 ourProps := *(unwrapValue(arguments[0].Get(nestedProps)).(*Props)) 279 cmp.ComponentWillReceivePropsIntf(ourProps) 280 } 281 282 return nil 283 })) 284 285 compDef.Set(reactCompComponentWillUnmount, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} { 286 elem := this 287 cmp := builder(ComponentDef{elem: elem}) 288 289 if cmp, ok := cmp.(componentWithWillUnmount); ok { 290 cmp.ComponentWillUnmount() 291 } 292 293 return nil 294 })) 295 296 compDef.Set(reactCompComponentWillMount, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} { 297 elem := this 298 cmp := builder(ComponentDef{elem: elem}) 299 300 // TODO we can make this more efficient by not doing the type check 301 // within the function body; it is known at the time of setting 302 // "componentWillMount" on the compDef 303 if cmp, ok := cmp.(componentWithWillMount); ok { 304 cmp.ComponentWillMount() 305 } 306 307 return nil 308 })) 309 310 compDef.Set(reactCompRender, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} { 311 elem := this 312 cmp := builder(ComponentDef{elem: elem}) 313 314 renderRes := cmp.RendersElement() 315 316 return renderRes 317 })) 318 319 return react.Call(reactCreateClass, compDef) 320 } 321 322 func Render(el Element, container dom.Element) Element { 323 v := reactDOM.Call(reactDOMRender, el, container) 324 325 return &elementHolder{Elem: v} 326 }