-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
React context-like feature #4029
Comments
Why would you need contexts with Vue? |
Afaik, there currently isn't a way to implicitly pass down props. Current options:
|
Possibly interesting alternative to context: plugins / mixins that apply to a component and all of it's descendants, instead of globally |
@sebastiandedeyne Functional components already have this context feature. (http://vuejs.org/guide/render-function#Functional-Components) Though currently normal components don't. |
Context in functional components only provides data from the direct parent (I don't think |
I think this feature is nice to have in Vue. |
To get the ball rolling; I needed this feature in a recent project, and solved it by writing two mixins: Vue.component('parent', {
template: '<div><child></child></div>',
data() {
return {
user: {
name: 'Sebastian',
},
};
},
mixins: [
expose(vm => ({
user: this.user,
})),
],
});
Vue.component('child', {
template: '<div>{{ $user.name }}</div>',
mixins: [
inject('foo'),
],
}); |
@sebastiandedeyne that is quite an interesting approach. Maybe something like: var parent = {
...,
expose: ['user'],
};
var child = {
...,
inject: ['user'],
}; |
That would be the same as passing props. |
Let's say that, hypothetically, Vuex wouldn't be implemented as a global plugin, but as something that can be applied on a per-component basis.
I'm looking for a mechanism to have a property based on something that a parent—an ancestor—component defines, not necessarily the direct parent. |
Of course we can do the same thing by explicitly passing values to props but I think we sometimes need such implicit data propagation. In my case, for example, I am writing canvas rendering abstraction library and it has components for shapes (rect, circle, etc...) rendered on canvas. The shape components have to be descendants of context component that has canvas DOM element. Example: <!--
context has canvas element and it will render all shape components in its descendants.
-->
<context width="300" height="300">
<rectangle x="10" y="20" width="50" height="50"></rectangle>
<group x="100" y="100">
<triangle v-for="t in triangle" :x="t.x" :y="t.y"></triangle>
</group>
</context> When the data of shape components are updated, I have to notify to context component through an event bus to re-render the canvas. To do so, I need such react's context-like feature to propagate an event bus because 1) it is quite verbose to write props for all shape components, 2) it needs its own event bus instance for each context component instance and 3) I don't want to manage this on global state management as this is local view's concern. |
Context is a convenient construct for library author and coupled components(for example tabs and tab-panes). For a library, it is quite strange and lame to encapsulate library local context in a separate global store. Library author might be able to take the burden of passing props all over, but components may also be an interface for end users. @ktsn has a good example and good arguments for context feature. But I have some questions about after reading React's context document. (disclaimer: I'm not a React user.) It seems the cooperation of But I still have questions on how context should be found. For example, should context only be found as a child's parent? Or ancestors? Or only closest ancestor? Also, should context in parent component be visible to all its children, in templates and slots? Or only to children in its template? Or only to children in slots? Or provide an option for users to choose? |
I have tried some toy implementation. It seems to be fully doable in user land without touching vue core. It requires a token for each expose and injection to avoid conflict. Other parts look like api suggested by @simplesmiler and @sebastiandedeyne var user = createToken('user')
var parent = {
....
expose: (vm) => ({[user]: this.user}) // computed property key for token
}
var child = {
inject: {user}
// {user: user},the first user is for property name, second is token value
} Context visibility is fully controlled by token. If parent component does not expose token, context is not visible to any child. Another helper method I agree with @posva that context is very delicate and can be replaced by passing props. Including it by default promotes bad practice to beginner. If advanced users do need it in library code, the implementation overhead is just few lines of code. |
I have my doubts about the token. While I understand that it's useful to avoid conflicts, it's an object that needs to be passed around throughout components—which is exactly what we're trying to avoid. What would this look like assuming components live in separate files (so you can't just declare the token variable on top, but need to pass it around somehow)? Or is the token not required? |
Because token is just a primitive value (symbol in ES6 environment or string otherwise), it can be declared outside of component and imported. So only component used the context feature will need to import token. Intermediate components will be agnostic about token and context. No need to passing around token. For example, the A Token itself is globally unique, but context object retrieved by the token is component specific and stored only in relevant components. |
Makes sense. I still believe strings are good enough in most situations though, but being compatible with symbols is a good addition for those that want something more 'strict'. I'm working on a package for this to use throughout my own projects (will be open sourced too), but I'd still prefer to see something official for this, either in the core or as an official package :) |
I think it's an excellent proposal. Of course something like this should be used with caution. |
In addition to the example by @ktsn this approach can be useful to act similar to a Service Locator to decouple components from services and avoid to "require()" a service instance (as I read as solution in some examples)
Using the approach from @HerringtonDarkholme I actually solved the issue with this proof of concept implementation
Beside the implementation as a plugin, which uses an awful global container, defining the services as a key value pairs remind me the data props definition and open the more complex features (like querying for a feature or interface). |
In case anyone's interested, in the mean time I created a package based on what has been discussed for our own projects: https://github.com/spatie/vue-expose-inject |
I support this feature request. Definitely something I could have used on a past project. Something native to Vue would be really cool. |
+1 |
Since it is doable in user land, I think there is no need to do it in core. See comment abowe for additional information. |
I started to think this feature should be desirably supported by official. (Either builtin or by official plugin). The usage is quite different from those mentioned above. In testing, I want to replace some dependencies imported from other files. Now the vue-loader official doc recommends to use inject-loader. However, it depends on specific build system like webpack. And inject-loader is quite of black magic. If context feature is supported, it can act like a dependency injection system, naturally. And in test we can inject mock into component easily and universally, without hacking build system. Another issue is discovered in SSR. Currently bundled-render uses |
As for me Angulars' DI is very flexible but complicated. On the other hand Reacts' context is more simple to use. If this feature will be implemented, I would expect to have the best from two worlds in Vue. But I still think that we don't need something simple enough which can be implemented in user land. |
It may be simple to implement in user land, but the main reason I want this in core or an official package is so we have a standardised solution. |
FYI: this can now be accomplished using provide / inject |
Whatever solution is arrived at, please please, avoid matching contexts by string name. Instead, match context by reference. This prevents naming collisions/conflicts, and is one major thing React fixed from their first context implementation to their second. For reference, here's my small context implementation for SkateJS (but fairly generic and usable anywhere outside SkateJS): https://codepen.io/trusktr/project/editor/XbEOMk The Notice in particular that the providers are passed by reference. This makes them entirely unique and clash free (like with React's new Contexts). In my sample, contexts are not used declaratively like they are in React's JSX version. In Vue, because it compiles to references, it could be made to be declarative, though personally I don't like having to declare contexts in markup as we will be viewing the JavaScript already anyways, and don't benefit much from the markup in my opinion. In my sample, everything is in the JavaScript code, similar to @sebastiandedeyne's idea. Notice also that to we get a specific context by reference: Avoid strings. When you start putting contexts in the middle of a large app and there's a name conflict, it will be a headache, especially if the props inside the context are different. References in React JSX completely get around this problem 100%. |
Ah shoot, I see Vue already made the mistake with provide/inject. I guess it's easy to use, but let's see what happens in big apps... |
Another thing to note, in my SkateJS implementation, components react to specific props, rather than reacting to a context as a whole value. This is nice because it prevents unnecessary re-renders of components. Some components may only care about a subset of props of a context, and in my implementation only components subscribed to those props will update. This prevents needing a By doing prop-based subscription, you can also prevent from creating new Objects every update. This is especially good for rapidly updating components (f.e. during animations that are intended to be 60fps), so that we don't have running memory growth causing garbage collection and jank. In my implementation sample, the context object never changes, so memory use can stay constant during animations if the props are all primitive values. It'd be great for provide/inject to be reference-based, and for components to be able to subscribe to specific properties. In React apps that I've seen, contexts are usually Objects, not primitive values. People end up recreating whole new objects just to trigger updates and these updates update all components that don't even care about all changed props. This is unnecessary CPU/Memory waste. |
I have published an NPM package for abstracting the |
Hey, leoyli Could you please give me some tips? I'm using Vue2, Typescript and JSX and trying to use your library. Thank you and best wishes. |
Hi! |
Would a React context-like feature ever be added?
Context is an implicit way to pass down data from a component without having to pass them through props on every level.
https://facebook.github.io/react/docs/context.html
It's generally not a good idea to use context for application-specific logic, but it's very useful for objects like stores.
Currently working on a helper mixin that achieves something similar, but would prefer to see this baked in the core.
The text was updated successfully, but these errors were encountered: